Compare commits

...
Author SHA1 Message Date
Roger Barreto 469525d9e6 Update 3 2025-11-07 21:53:36 +00:00
Roger Barreto 6e04c6bbbe Update 2 2025-11-07 21:44:31 +00:00
Roger Barreto f5d6056074 Update 2025-11-07 20:01:38 +00:00
Roger Barreto 9586a3ea53 Bugbash Samples 2025-11-07 17:51:17 +00:00
Roger Barreto 82bd9cf6b5 Restructure Step1 2025-11-07 12:46:04 +00:00
ChrisandGitHub 4cbde55243 Merge branch 'main' into feature-foundry-agents 2025-11-06 18:48:17 -08:00
Chris Rickman 864b1f7a91 Fix bad merge 2025-11-06 18:40:34 -08:00
ChrisandGitHub 5131f3a129 Merge branch 'main' into feature-foundry-agents 2025-11-06 14:31:48 -08:00
ChrisandGitHub 2ee34beed5 Merge branch 'main' into feature-foundry-agents 2025-11-06 08:32:05 -08:00
Roger BarretoandGitHub d55b15903d .NET: AgentDefinition extensions method simplification (#1967)
* Update extensions methods that accepts AgentDefinition type to not be restrictive

* Update Unit Tests

* Revert yarn/package-lock

* Revert yarn/package-lock

* Address copilot feedback
2025-11-06 16:04:31 +00:00
ChrisandGitHub 5a8c8fe634 Merge branch 'main' into feature-foundry-agents 2025-11-05 19:55:57 -08:00
ChrisandGitHub b2c38ac98c Merge branch 'main' into feature-foundry-agents 2025-11-05 19:14:20 -08:00
ChrisandGitHub 1aa00e6428 Merge branch 'main' into feature-foundry-agents 2025-11-05 16:57:09 -08:00
ChrisandGitHub f0d2dd6774 Merge branch 'main' into feature-foundry-agents 2025-11-05 15:49:58 -08:00
ChrisandGitHub bf007f854c Updated (#1948) 2025-11-05 15:47:50 -08:00
ChrisandGitHub 55a4aa2b53 Merge branch 'main' into feature-foundry-agents 2025-11-05 12:26:13 -08:00
Roger Barreto 95fe891369 Normalize changes 2025-11-05 20:20:48 +00:00
Roger Barreto 4d1a132737 .NET: Allow Declarative AIAgents Extensions (#1931)
* Improve reusability of extension code and additional option to losen the strictiness of in-proc tools

* Add missing UT scenarios

* Add missing UT test scenarios
2025-11-05 20:14:23 +00:00
Dmytro StrukandRoger Barreto 943e37836f Updated package version (#1906) 2025-11-05 20:14:22 +00:00
Roger Barreto 65e1c12dfa Version update (#1901) 2025-11-05 20:14:21 +00:00
Roger BarretoandCopilot 279d91f58e .NET: Update Extensions for Strict Agent Definitions + Improvements (#1892)
* Update Package Nameing: V1 -> AzureAI.Persistent / V2 -> AzureAI

* Update agents and extensions to comply with strict agent definitions

* More static updates

* Address UT, and ResponseTool support

* Improving reusability extensions

* Addressing ResponseTools Unit Tests and extension setup

* Adapted workaround on breaking AAA with OpenAI 2.6.0

* Small updates

* Remove strictness when retrieving agents, improved XmlDocs

* Improve sample comments

* Update dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AgentsClientExtensionsTests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Address PR comments

* Address UT failing

* Address Copilot feedback

* Address Copilot feedback

* Address comment typo

* Address PR feedback

* Address typo

* Add missing Extensions with ChatClientAgentOptions

* Address comments

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-05 20:14:12 +00:00
Roger Barreto 51e7a2134a Update Package Nameing: V1 -> AzureAI.Persistent / V2 -> AzureAI (#1829) 2025-11-05 20:13:36 +00:00
Roger Barreto 90226526fa .NET: Change model to be required just for prompt agent definition specific extensions (#1812)
* Remove unneeded model from extensions

* Add noop justification
2025-11-05 20:11:43 +00:00
CopilotRoger BarretoCopilotcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
7405759593 .NET: Add comprehensive unit tests for Microsoft.Agents.AI.AzureAIAgents extension methods (#1786)
* Initial plan

* Add comprehensive unit test project for Microsoft.Agents.AI.AzureAIAgents

Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>

* Add README documenting test project and package dependency requirements

Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>

* Fix documentation URL to use learn.microsoft.com

Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>

* Bump back AAAP 1.2.0-beta.7

* Address AI generated UT's

* Remove UT Readme

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-05 20:11:32 +00:00
ChrisandRoger Barreto 698aba5f97 Update Directory.Packages.props
Fix package version rollback:

Azure.AI.Agents.Persistent (beta-6 => beta-7)
2025-11-05 20:10:31 +00:00
Roger Barreto 7e23140ca9 .NET: Azure.AI.Agents Package Split + Initial Extensions (#1657)
* Move packages

* Update nuget.config

* Address Xmldoc

* Remove format from branches checks

* Address Xmldocs

* Add more details to the implementation

* Moving Agent logic to ChatClient

* Adding Name and Id overrides to AzureAIAgent

* Updating extensions

* Add GetAiAgent extensions

* Adding support for version as name can conflict 409 using the Agents API with same name

* Addressing more updates to the extensions

* More improvements

* Remove debugging code from sample

* Address copilot feedback

* Apply suggestions from co-pilot code review
2025-11-05 20:10:24 +00:00
ChrisandRoger Barreto 904e17473f Fixed build errors (#1638)
Comment and nullable type alignment
2025-11-05 20:09:02 +00:00
Roger Barreto 2c5cf6c67b WIP 2025-11-05 20:08:55 +00:00
46 changed files with 4820 additions and 5 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ name: dotnet-format
on:
workflow_dispatch:
pull_request:
branches: ["main", "feature*"]
branches: ["main"]
paths:
- dotnet/**
- '.github/workflows/dotnet-format.yml'
+2 -1
View File
@@ -17,6 +17,7 @@
<PackageVersion Include="Aspire.Microsoft.Azure.Cosmos" Version="$(AspireAppHostSdkVersion)" />
<PackageVersion Include="CommunityToolkit.Aspire.OllamaSharp" Version="9.9.0" />
<!-- Azure.* -->
<PackageVersion Include="Azure.AI.Agents" Version="2.0.0-alpha.20251104.7" />
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.7" />
<PackageVersion Include="Azure.AI.OpenAI" Version="2.5.0-beta.1" />
<PackageVersion Include="Azure.Identity" Version="1.17.0" />
@@ -151,4 +152,4 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
</Project>
+19 -1
View File
@@ -43,6 +43,23 @@
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_OpenAIChatCompletion/Agent_With_OpenAIChatCompletion.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_OpenAIResponses/Agent_With_OpenAIResponses.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AgentProviders/AzureAIAgents/">
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step01.1_Basics/AzureAIAgents_Step01.1_Basics.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step01.2_Running/AzureAIAgents_Step01.2_Running.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step02_MultiturnConversation/AzureAIAgents_Step02_MultiturnConversation.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step03.1_UsingFunctionTools/AzureAIAgents_Step03.1_UsingFunctionTools.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step03.2_UsingFunctionTools_FromOpenAPI/AzureAIAgents_Step03.2_UsingFunctionTools_FromOpenAPI.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step04_UsingFunctionToolsWithApprovals/AzureAIAgents_Step04_UsingFunctionToolsWithApprovals.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step05_StructuredOutput/AzureAIAgents_Step05_StructuredOutput.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step06_PersistedConversations/AzureAIAgents_Step06_PersistedConversations.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step07_Observability/AzureAIAgents_Step07_Observability.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step08_DependencyInjection/AzureAIAgents_Step08_DependencyInjection.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step09_AsMcpTool/AzureAIAgents_Step09_AsMcpTool.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step10_UsingImages/AzureAIAgents_Step10_UsingImages.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step11_AsFunctionTool/AzureAIAgents_Step11_AsFunctionTool.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step12_Middleware/AzureAIAgents_Step12_Middleware.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step13_Plugins/AzureAIAgents_Step13_Plugins.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/Agents/">
<File Path="samples/GettingStarted/Agents/README.md" />
<Project Path="samples/GettingStarted/Agents/Agent_Step01_Running/Agent_Step01_Running.csproj" />
@@ -61,7 +78,6 @@
<Project Path="samples/GettingStarted/Agents/Agent_Step13_Memory/Agent_Step13_Memory.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step14_Middleware/Agent_Step14_Middleware.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step15_Plugins/Agent_Step15_Plugins.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Agent_Step16_ChatReduction.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/Agent_Step17_BackgroundResponses.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step18_TextSearchRag/Agent_Step18_TextSearchRag.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step19_Mem0Provider/Agent_Step19_Mem0Provider.csproj" />
@@ -283,6 +299,7 @@
<Project Path="src/Microsoft.Agents.AI.Abstractions/Microsoft.Agents.AI.Abstractions.csproj" />
<Project Path="src/Microsoft.Agents.AI.AGUI/Microsoft.Agents.AI.AGUI.csproj" />
<Project Path="src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj" />
<Project Path="src/Microsoft.Agents.AI.AzureAI/Microsoft.Agents.AI.AzureAI.csproj" />
<Project Path="src/Microsoft.Agents.AI.CopilotStudio/Microsoft.Agents.AI.CopilotStudio.csproj" />
<Project Path="src/Microsoft.Agents.AI.DevUI/Microsoft.Agents.AI.DevUI.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj" />
@@ -313,6 +330,7 @@
<Project Path="tests/Microsoft.Agents.AI.Abstractions.UnitTests/Microsoft.Agents.AI.Abstractions.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.AGUI.UnitTests/Microsoft.Agents.AI.AGUI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.AzureAI.UnitTests/Microsoft.Agents.AI.AzureAI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.A2A.Tests/Microsoft.Agents.AI.Hosting.A2A.Tests.csproj" Id="2a1c544d-237d-4436-8732-ba0c447ac06b" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests.csproj" />
+4
View File
@@ -3,10 +3,14 @@
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="azure-sdk-for-net" value="https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="azure-sdk-for-net">
<package pattern="Azure.AI.Agents" />
</packageSource>
</packageSourceMapping>
</configuration>
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);IDE0059</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Agents" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,54 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows how to create and use a AI agents with Azure Foundry Agents as the backend.
using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
const string JokerInstructions = "You are good at telling jokes.";
const string JokerName = "JokerAgent";
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
// Define the agent you want to create. (Prompt Agent in this case)
var agentDefinition = new PromptAgentDefinition(model: deploymentName) { Instructions = JokerInstructions };
// Azure.AI.Agents SDK creates and manages agent by name and versions.
// You can create a server side agent version with the Azure.AI.Agents SDK client below.
var agentVersion = agentsClient.CreateAgentVersion(agentName: JokerName, definition: agentDefinition);
// Note:
// agentVersion.Id = "<agentName>:<versionNumber>",
// agentVersion.Version = <versionNumber>,
// agentVersion.Name = <agentName>
// You can retrieve an AIAgent for a already created server side agent version.
AIAgent jokerAgentV1 = agentsClient.GetAIAgent(agentVersion);
// You can also create another AIAgent version (V2) by providing the same name with a different definition.
AIAgent jokerAgentV2 = agentsClient.CreateAIAgent(name: JokerName, model: deploymentName, instructions: JokerInstructions + "V2");
// You can also get the AIAgent latest version just providing its name.
AIAgent jokerAgentLatest = agentsClient.GetAIAgent(name: JokerName);
var latestVersion = jokerAgentLatest.GetService<AgentVersion>()!;
// The AIAgent version can be accessed via the GetService method.
Console.WriteLine($"Latest agent version id: {latestVersion.Id}");
// Once you have the AIAgent, you can invoke it like any other AIAgent.
AgentThread thread = jokerAgentLatest.GetNewThread();
Console.WriteLine(await jokerAgentLatest.RunAsync("Tell me a joke about a pirate.", thread));
// This will use the same thread to continue the conversation.
Console.WriteLine(await jokerAgentLatest.RunAsync("Now tell me a joke about a cat and a dog using last joke as the anchor.", thread));
// Cleanup by agent name removes both agent versions created (jokerAgentV1 + jokerAgentV2).
agentsClient.DeleteAgent(jokerAgentV1.Name);
// It is also possible delete just a specific agent version by the composition (name + version number).
// agentsClient.DeleteAgentVersion(latestVersion.Name, latestVersion.Version);
@@ -0,0 +1,16 @@
# Prerequisites
Before you begin, ensure you have the following prerequisites:
- .NET 8.0 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Azure Foundry resource. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
Set the following environment variables:
```powershell
$env:AZURE_FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project" # Replace with your Azure Foundry resource endpoint
$env:AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
```
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Agents" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows how to create and use a simple AI agent with Azure Foundry Agents as the backend.
using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
const string JokerInstructions = "You are good at telling jokes.";
const string JokerName = "JokerAgent";
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
// Define the agent you want to create. (Prompt Agent in this case)
var agentDefinition = new PromptAgentDefinition(model: deploymentName) { Instructions = JokerInstructions };
// Azure.AI.Agents SDK creates and manages agent by name and versions.
// You can create a server side agent version with the Azure.AI.Agents SDK client below.
var agentVersion = agentsClient.CreateAgentVersion(agentName: JokerName, definition: agentDefinition);
// You can retrieve an AIAgent for a already created server side agent version.
AIAgent jokerAgent = agentsClient.GetAIAgent(agentVersion);
// Invoke the agent and output the text result.
AgentThread thread = jokerAgent.GetNewThread();
Console.WriteLine(await jokerAgent.RunAsync("Tell me a joke about a pirate.", thread));
// Invoke the agent with streaming support.
thread = jokerAgent.GetNewThread();
await foreach (var update in jokerAgent.RunStreamingAsync("Tell me a joke about a pirate.", thread))
{
Console.WriteLine(update);
}
// Cleanup by agent name removes the agent version created.
agentsClient.DeleteAgent(jokerAgent.Name);
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Agents" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,44 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows how to create and use a simple AI agent with a multi-turn conversation.
using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
const string JokerInstructions = "You are good at telling jokes.";
const string JokerName = "JokerAgent";
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
// Define the agent you want to create. (Prompt Agent in this case)
var agentDefinition = new PromptAgentDefinition(model: deploymentName) { Instructions = JokerInstructions };
// Create a server side agent version with the Azure.AI.Agents SDK client.
var agentVersion = agentsClient.CreateAgentVersion(agentName: JokerName, definition: agentDefinition);
// Retrieve an AIAgent for the created server side agent version.
AIAgent jokerAgent = agentsClient.GetAIAgent(agentVersion);
// Invoke the agent with a multi-turn conversation, where the context is preserved in the thread object.
AgentThread thread = jokerAgent.GetNewThread();
Console.WriteLine(await jokerAgent.RunAsync("Tell me a joke about a pirate.", thread));
Console.WriteLine(await jokerAgent.RunAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", thread));
// Invoke the agent with a multi-turn conversation and streaming, where the context is preserved in the thread object.
thread = jokerAgent.GetNewThread();
await foreach (var update in jokerAgent.RunStreamingAsync("Tell me a joke about a pirate.", thread))
{
Console.WriteLine(update);
}
await foreach (var update in jokerAgent.RunStreamingAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", thread))
{
Console.WriteLine(update);
}
// Cleanup by agent name removes the agent version created.
agentsClient.DeleteAgent(jokerAgent.Name);
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Agents" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,43 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates how to use an agent with function tools.
// It shows both non-streaming and streaming agent interactions using weather-related tools.
using System.ComponentModel;
using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
=> $"The weather in {location} is cloudy with a high of 15°C.";
const string AssistantInstructions = "You are a helpful assistant that can get weather information.";
const string AssistantName = "WeatherAssistant";
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
// Define the agent with function tools.
var tool = AIFunctionFactory.Create(GetWeather);
// Create AIAgent directly
AIAgent agent = await agentsClient.CreateAIAgentAsync(name: AssistantName, model: deploymentName, instructions: AssistantInstructions, tools: [tool]);
// Non-streaming agent interaction with function tools.
AgentThread thread = agent.GetNewThread();
Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?", thread));
// Streaming agent interaction with function tools.
thread = agent.GetNewThread();
await foreach (var update in agent.RunStreamingAsync("What is the weather like in Amsterdam?", thread))
{
Console.WriteLine(update);
}
// Cleanup by agent name removes the agent version created.
agentsClient.DeleteAgent(agent.Name);
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Agents" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.SemanticKernel.Plugins.OpenApi" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="OpenAPISpec.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,354 @@
{
"openapi": "3.0.1",
"info": {
"title": "Github Versions API",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.github.com"
}
],
"components": {
"schemas": {
"basic-error": {
"title": "Basic Error",
"description": "Basic Error",
"type": "object",
"properties": {
"message": {
"type": "string"
},
"documentation_url": {
"type": "string"
},
"url": {
"type": "string"
},
"status": {
"type": "string"
}
}
},
"label": {
"title": "Label",
"description": "Color-coded labels help you categorize and filter your issues (just like labels in Gmail).",
"type": "object",
"properties": {
"id": {
"description": "Unique identifier for the label.",
"type": "integer",
"format": "int64",
"example": 208045946
},
"node_id": {
"type": "string",
"example": "MDU6TGFiZWwyMDgwNDU5NDY="
},
"url": {
"description": "URL for the label",
"example": "https://api.github.com/repositories/42/labels/bug",
"type": "string",
"format": "uri"
},
"name": {
"description": "The name of the label.",
"example": "bug",
"type": "string"
},
"description": {
"description": "Optional description of the label, such as its purpose.",
"type": "string",
"example": "Something isn't working",
"nullable": true
},
"color": {
"description": "6-character hex code, without the leading #, identifying the color",
"example": "FFFFFF",
"type": "string"
},
"default": {
"description": "Whether this label comes by default in a new repository.",
"type": "boolean",
"example": true
}
},
"required": [
"id",
"node_id",
"url",
"name",
"description",
"color",
"default"
]
},
"tag": {
"title": "Tag",
"description": "Tag",
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "v0.1"
},
"commit": {
"type": "object",
"properties": {
"sha": {
"type": "string"
},
"url": {
"type": "string",
"format": "uri"
}
},
"required": [
"sha",
"url"
]
},
"zipball_url": {
"type": "string",
"format": "uri",
"example": "https://github.com/octocat/Hello-World/zipball/v0.1"
},
"tarball_url": {
"type": "string",
"format": "uri",
"example": "https://github.com/octocat/Hello-World/tarball/v0.1"
},
"node_id": {
"type": "string"
}
},
"required": [
"name",
"node_id",
"commit",
"zipball_url",
"tarball_url"
]
}
},
"examples": {
"label-items": {
"value": [
{
"id": 208045946,
"node_id": "MDU6TGFiZWwyMDgwNDU5NDY=",
"url": "https://api.github.com/repos/octocat/Hello-World/labels/bug",
"name": "bug",
"description": "Something isn't working",
"color": "f29513",
"default": true
},
{
"id": 208045947,
"node_id": "MDU6TGFiZWwyMDgwNDU5NDc=",
"url": "https://api.github.com/repos/octocat/Hello-World/labels/enhancement",
"name": "enhancement",
"description": "New feature or request",
"color": "a2eeef",
"default": false
}
]
},
"tag-items": {
"value": [
{
"name": "v0.1",
"commit": {
"sha": "c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc",
"url": "https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc"
},
"zipball_url": "https://github.com/octocat/Hello-World/zipball/v0.1",
"tarball_url": "https://github.com/octocat/Hello-World/tarball/v0.1",
"node_id": "MDQ6VXNlcjE="
}
]
}
},
"parameters": {
"owner": {
"name": "owner",
"description": "The account owner of the repository. The name is not case sensitive.",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
"repo": {
"name": "repo",
"description": "The name of the repository without the `.git` extension. The name is not case sensitive.",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
"per-page": {
"name": "per_page",
"description": "The number of results per page (max 100). For more information, see \"[Using pagination in the REST API](https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api).\"",
"in": "query",
"schema": {
"type": "integer",
"default": 30
}
},
"page": {
"name": "page",
"description": "The page number of the results to fetch. For more information, see \"[Using pagination in the REST API](https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api).\"",
"in": "query",
"schema": {
"type": "integer",
"default": 1
}
}
},
"responses": {
"not_found": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/basic-error"
}
}
}
}
},
"headers": {
"link": {
"example": "<https://api.github.com/resource?page=2>; rel=\"next\", <https://api.github.com/resource?page=5>; rel=\"last\"",
"schema": {
"type": "string"
}
}
}
},
"paths": {
"/repos/{owner}/{repo}/tags": {
"get": {
"summary": "List repository tags",
"description": "",
"tags": [
"repos"
],
"operationId": "repos/list-tags",
"externalDocs": {
"description": "API method documentation",
"url": "https://docs.github.com/rest/repos/repos#list-repository-tags"
},
"parameters": [
{
"$ref": "#/components/parameters/owner"
},
{
"$ref": "#/components/parameters/repo"
},
{
"$ref": "#/components/parameters/per-page"
},
{
"$ref": "#/components/parameters/page"
}
],
"responses": {
"200": {
"description": "Response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/tag"
}
},
"examples": {
"default": {
"$ref": "#/components/examples/tag-items"
}
}
}
},
"headers": {
"Link": {
"$ref": "#/components/headers/link"
}
}
}
},
"x-github": {
"githubCloudOnly": false,
"enabledForGitHubApps": true,
"category": "repos",
"subcategory": "repos"
}
}
},
"/repos/{owner}/{repo}/labels": {
"get": {
"summary": "List labels for a repository",
"description": "Lists all labels for a repository.",
"tags": [
"issues"
],
"operationId": "issues/list-labels-for-repo",
"externalDocs": {
"description": "API method documentation",
"url": "https://docs.github.com/rest/issues/labels#list-labels-for-a-repository"
},
"parameters": [
{
"$ref": "#/components/parameters/owner"
},
{
"$ref": "#/components/parameters/repo"
},
{
"$ref": "#/components/parameters/per-page"
},
{
"$ref": "#/components/parameters/page"
}
],
"responses": {
"200": {
"description": "Response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/label"
}
},
"examples": {
"default": {
"$ref": "#/components/examples/label-items"
}
}
}
},
"headers": {
"Link": {
"$ref": "#/components/headers/link"
}
}
},
"404": {
"$ref": "#/components/responses/not_found"
}
},
"x-github": {
"githubCloudOnly": false,
"enabledForGitHubApps": true,
"category": "issues",
"subcategory": "labels"
}
}
}
}
}
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates how to use an agent with function tools provided via an OpenAPI spec.
// It uses functionality from Semantic Kernel to parse the OpenAPI spec and create function tools to use with the Agent Framework Agent.
using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Plugins.OpenApi;
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// Load the OpenAPI Spec from a file.
KernelPlugin plugin = await OpenApiKernelPluginFactory.CreateFromOpenApiAsync("github", "OpenAPISpec.json");
// Convert the Semantic Kernel plugin to Agent Framework function tools.
// This requires a dummy Kernel instance, since KernelFunctions cannot execute without one.
Kernel kernel = new();
List<AITool> tools = plugin.Select(x => x.WithKernel(kernel)).Cast<AITool>().ToList();
const string AssistantInstructions = "You are a helpful assistant that can query GitHub repositories.";
const string AssistantName = "GitHubAssistant";
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
// Create AIAgent directly
AIAgent agent = await agentsClient.CreateAIAgentAsync(name: AssistantName, model: deploymentName, instructions: AssistantInstructions, tools: tools);
// Run the agent with the OpenAPI function tools.
AgentThread thread = agent.GetNewThread();
Console.WriteLine(await agent.RunAsync("Please list the names, colors and descriptions of all the labels available in the microsoft/agent-framework repository on github.", thread));
// Cleanup by agent name removes the agent version created.
agentsClient.DeleteAgent(agent.Name);
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Agents" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,64 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates how to use an agent with function tools that require a human in the loop for approvals.
// It shows both non-streaming and streaming agent interactions using weather-related tools.
// If the agent is hosted in a service, with a remote user, combine this sample with the Persisted Conversations sample to persist the chat history
// while the agent is waiting for user input.
using System.ComponentModel;
using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// Create a sample function tool that the agent can use.
[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
=> $"The weather in {location} is cloudy with a high of 15°C.";
const string AssistantInstructions = "You are a helpful assistant that can get weather information.";
const string AssistantName = "WeatherAssistant";
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
var approvalTool = new ApprovalRequiredAIFunction(AIFunctionFactory.Create(GetWeather));
// Create AIAgent directly
AIAgent agent = await agentsClient.CreateAIAgentAsync(name: AssistantName, model: deploymentName, instructions: AssistantInstructions, tools: [approvalTool]);
// Call the agent with approval-required function tools.
// The agent will request approval before invoking the function.
AgentThread thread = agent.GetNewThread();
var response = await agent.RunAsync("What is the weather like in Amsterdam?", thread);
// Check if there are any user input requests (approvals needed).
var userInputRequests = response.UserInputRequests.ToList();
while (userInputRequests.Count > 0)
{
// Ask the user to approve each function call request.
// For simplicity, we are assuming here that only function approval requests are being made.
var userInputMessages = userInputRequests
.OfType<FunctionApprovalRequestContent>()
.Select(functionApprovalRequest =>
{
Console.WriteLine($"The agent would like to invoke the following function, please reply Y to approve: Name {functionApprovalRequest.FunctionCall.Name}");
var approved = Console.ReadLine()?.Equals("Y", StringComparison.OrdinalIgnoreCase) ?? false;
return new ChatMessage(ChatRole.User, [functionApprovalRequest.CreateResponse(approved)]);
})
.ToList();
// Pass the user input responses back to the agent for further processing.
response = await agent.RunAsync(userInputMessages, thread);
userInputRequests = response.UserInputRequests.ToList();
}
Console.WriteLine($"\nAgent: {response}");
// Cleanup by agent name removes the agent version created.
agentsClient.DeleteAgent(agent.Name);
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Agents" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows how to configure an agent to produce structured output.
using System.ComponentModel;
using System.Text.Json;
using System.Text.Json.Serialization;
using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using SampleApp;
#pragma warning disable CA5399
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = "gpt-5"; // Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
const string AssistantInstructions = "You are a helpful assistant that extracts structured information about people.";
const string AssistantName = "StructuredOutputAssistant";
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
// Create ChatClientAgent directly
ChatClientAgent agent = await agentsClient.CreateAIAgentAsync(
model: deploymentName,
new ChatClientAgentOptions(name: AssistantName, instructions: AssistantInstructions));
// Set PersonInfo as the type parameter of RunAsync method to specify the expected structured output from the agent and invoke the agent with some unstructured input.
AgentRunResponse<PersonInfo> response = await agent.RunAsync<PersonInfo>("Please provide information about John Smith, who is a 35-year-old software engineer.");
// Access the structured output via the Result property of the agent response.
Console.WriteLine("Assistant Output:");
Console.WriteLine($"Name: {response.Result.Name}");
Console.WriteLine($"Age: {response.Result.Age}");
Console.WriteLine($"Occupation: {response.Result.Occupation}");
// Create the ChatClientAgent with the specified name, instructions, and expected structured output the agent should produce.
ChatClientAgent agentWithPersonInfo = agentsClient.CreateAIAgent(model: deploymentName, new ChatClientAgentOptions(name: AssistantName, instructions: AssistantInstructions)
{
ChatOptions = new()
{
ResponseFormat = Microsoft.Extensions.AI.ChatResponseFormat.ForJsonSchema<PersonInfo>()
}
});
// Invoke the agent with some unstructured input while streaming, to extract the structured information from.
var updates = agentWithPersonInfo.RunStreamingAsync("Please provide information about John Smith, who is a 35-year-old software engineer.");
// Assemble all the parts of the streamed output, since we can only deserialize once we have the full json,
// then deserialize the response into the PersonInfo class.
PersonInfo personInfo = (await updates.ToAgentRunResponseAsync()).Deserialize<PersonInfo>(JsonSerializerOptions.Web);
Console.WriteLine("Assistant Output:");
Console.WriteLine($"Name: {personInfo.Name}");
Console.WriteLine($"Age: {personInfo.Age}");
Console.WriteLine($"Occupation: {personInfo.Occupation}");
// Cleanup by agent name removes the agent version created.
agentsClient.DeleteAgent(agent.Name);
namespace SampleApp
{
/// <summary>
/// Represents information about a person, including their name, age, and occupation, matched to the JSON schema used in the agent.
/// </summary>
[Description("Information about a person including their name, age, and occupation")]
public class PersonInfo
{
[JsonPropertyName("name")]
public string? Name { get; set; }
[JsonPropertyName("age")]
public int? Age { get; set; }
[JsonPropertyName("occupation")]
public string? Occupation { get; set; }
}
}
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Agents" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,44 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows how to create and use a simple AI agent with a conversation that can be persisted to disk.
using System.Text.Json;
using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
const string JokerInstructions = "You are good at telling jokes.";
const string JokerName = "JokerAgent";
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
AIAgent agent = await agentsClient.CreateAIAgentAsync(name: JokerName, model: deploymentName, instructions: JokerInstructions);
// Start a new thread for the agent conversation.
AgentThread thread = agent.GetNewThread();
// Run the agent with a new thread.
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
// Serialize the thread state to a JsonElement, so it can be stored for later use.
JsonElement serializedThread = thread.Serialize();
// Save the serialized thread to a temporary file (for demonstration purposes).
string tempFilePath = Path.GetTempFileName();
await File.WriteAllTextAsync(tempFilePath, JsonSerializer.Serialize(serializedThread));
// Load the serialized thread from the temporary file (for demonstration purposes).
JsonElement reloadedSerializedThread = JsonSerializer.Deserialize<JsonElement>(await File.ReadAllTextAsync(tempFilePath));
// Deserialize the thread state after loading from storage.
AgentThread resumedThread = agent.DeserializeThread(reloadedSerializedThread);
// Run the agent again with the resumed thread.
Console.WriteLine(await agent.RunAsync("Now tell the same joke in the voice of a pirate, and add some emojis to the joke.", resumedThread));
// Cleanup by agent name removes the agent version created.
agentsClient.DeleteAgent(agent.Name);
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Agents" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" />
<PackageReference Include="OpenTelemetry" />
<PackageReference Include="OpenTelemetry.Exporter.Console" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,52 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows how to create and use a simple AI agent with Azure Foundry Agents as the backend that logs telemetry using OpenTelemetry.
using Azure.AI.Agents;
using Azure.Identity;
using Azure.Monitor.OpenTelemetry.Exporter;
using Microsoft.Agents.AI;
using OpenTelemetry;
using OpenTelemetry.Trace;
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
var applicationInsightsConnectionString = Environment.GetEnvironmentVariable("APPLICATIONINSIGHTS_CONNECTION_STRING");
const string JokerInstructions = "You are good at telling jokes.";
const string JokerName = "JokerAgent";
// Create TracerProvider with console exporter
// This will output the telemetry data to the console.
string sourceName = Guid.NewGuid().ToString("N");
var tracerProviderBuilder = Sdk.CreateTracerProviderBuilder()
.AddSource(sourceName)
.AddConsoleExporter();
if (!string.IsNullOrWhiteSpace(applicationInsightsConnectionString))
{
tracerProviderBuilder.AddAzureMonitorTraceExporter(options => options.ConnectionString = applicationInsightsConnectionString);
}
using var tracerProvider = tracerProviderBuilder.Build();
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
// Define the agent you want to create. (Prompt Agent in this case)
AIAgent agent = agentsClient.CreateAIAgent(name: JokerName, model: deploymentName, instructions: JokerInstructions)
.AsBuilder()
.UseOpenTelemetry(sourceName: sourceName)
.Build();
// Invoke the agent and output the text result.
AgentThread thread = agent.GetNewThread();
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
// Invoke the agent with streaming support.
thread = agent.GetNewThread();
await foreach (var update in agent.RunStreamingAsync("Tell me a joke about a pirate.", thread))
{
Console.WriteLine(update);
}
// Cleanup by agent name removes the agent version created.
agentsClient.DeleteAgent(agent.Name);
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Agents" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,80 @@
// Copyright (c) Microsoft. All rights reserved.
#pragma warning disable CA1812
// This sample shows how to use dependency injection to register an AIAgent and use it from a hosted service with a user input chat loop.
using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
const string JokerInstructions = "You are good at telling jokes.";
const string JokerName = "JokerAgent";
// Create a host builder that we will register services with and then run.
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
// Add the agents client to the service collection.
builder.Services.AddSingleton((sp) => new AgentsClient(new Uri(endpoint), new AzureCliCredential()));
// Add the AI agent to the service collection.
builder.Services.AddSingleton<AIAgent>((sp)
=> sp.GetRequiredService<AgentsClient>()
.CreateAIAgent(name: JokerName, model: deploymentName, instructions: JokerInstructions));
// Add a sample service that will use the agent to respond to user input.
builder.Services.AddHostedService<SampleService>();
// Build and run the host.
using IHost host = builder.Build();
await host.RunAsync().ConfigureAwait(false);
/// <summary>
/// A sample service that uses an AI agent to respond to user input.
/// </summary>
internal sealed class SampleService(AIAgent agent, IHostApplicationLifetime appLifetime) : IHostedService
{
private AgentThread? _thread;
public async Task StartAsync(CancellationToken cancellationToken)
{
// Create a thread that will be used for the entirety of the service lifetime so that the user can ask follow up questions.
this._thread = agent.GetNewThread();
_ = this.RunAsync(appLifetime.ApplicationStopping);
}
public async Task RunAsync(CancellationToken cancellationToken)
{
// Delay a little to allow the service to finish starting.
await Task.Delay(100, cancellationToken);
while (!cancellationToken.IsCancellationRequested)
{
Console.WriteLine("\nAgent: Ask me to tell you a joke about a specific topic. To exit just press Ctrl+C or enter without any input.\n");
Console.Write("> ");
var input = Console.ReadLine();
// If the user enters no input, signal the application to shut down.
if (string.IsNullOrWhiteSpace(input))
{
appLifetime.StopApplication();
break;
}
// Stream the output to the console as it is generated.
await foreach (var update in agent.RunStreamingAsync(input, this._thread, cancellationToken: cancellationToken))
{
Console.Write(update);
}
Console.WriteLine();
}
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>3afc9b74-af74-4d8e-ae96-fa1c511d11ac</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Agents.Persistent" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="ModelContextProtocol" />
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-rc.2.25502.107" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows how to expose an AI agent as an MCP tool.
using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Server;
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
const string JokerInstructions = "You are good at telling jokes, and you always start each joke with 'Aye aye, captain!'.";
const string JokerName = "JokerAgent";
const string JokerDescription = "An agent that tells jokes.";
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
// Define the agent you want to create. (Prompt Agent in this case)
AIAgent agent = agentsClient.CreateAIAgent(
name: JokerName,
model: deploymentName,
instructions: JokerInstructions,
creationOptions: new() { Description = JokerDescription });
// Convert the agent to an AIFunction and then to an MCP tool.
// The agent name and description will be used as the mcp tool name and description.
McpServerTool tool = McpServerTool.Create(agent.AsAIFunction());
// Register the MCP server with StdIO transport and expose the tool via the server.
HostApplicationBuilder builder = Host.CreateEmptyApplicationBuilder(settings: null);
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithTools([tool]);
Console.WriteLine("Starting MCP Tool server. Press Ctrl+C to exit.");
await builder.Build().RunAsync();
@@ -0,0 +1,29 @@
This sample demonstrates how to expose an existing AI agent as an MCP tool.
## Run the sample
To run the sample, please use one of the following MCP clients: https://modelcontextprotocol.io/clients
Alternatively, use the QuickstartClient sample from this repository: https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartClient
## Run the sample using MCP Inspector
To use the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector), follow these steps:
1. Open a terminal in the Agent_Step10_AsMcpTool project directory.
1. Run the `npx @modelcontextprotocol/inspector dotnet run` command to start the MCP Inspector. Make sure you have [node.js](https://nodejs.org/en/download/) and npm installed.
```bash
npx @modelcontextprotocol/inspector dotnet run
```
1. When the inspector is running, it will display a URL in the terminal, like this:
```
MCP Inspector is up and running at http://127.0.0.1:6274
```
1. Open a web browser and navigate to the URL displayed in the terminal. If not opened automatically, this will open the MCP Inspector interface.
1. In the MCP Inspector interface, add the following environment variables to allow your MCP server to access Azure AI Foundry Project to create and run the agent:
- AZURE_FOUNDRY_PROJECT_ENDPOINT = https://your-resource.openai.azure.com/ # Replace with your Azure AI Foundry Project endpoint
- AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME = gpt-4o-mini # Replace with your model deployment name
1. Find and click the `Connect` button in the MCP Inspector interface to connect to the MCP server.
1. As soon as the connection is established, open the `Tools` tab in the MCP Inspector interface and select the `Joker` tool from the list.
1. Specify your prompt as a value for the `query` argument, for example: `Tell me a joke about a pirate` and click the `Run Tool` button to run the tool.
1. The agent will process the request and return a response in accordance with the provided instructions that instruct it to always start each joke with 'Aye aye, captain!'.
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Agents" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows how to use Image Multi-Modality with an AI agent.
using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o";
const string VisionInstructions = "You are a helpful agent that can analyze images";
const string VisionName = "VisionAgent";
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
// Define the agent you want to create. (Prompt Agent in this case)
AIAgent agent = agentsClient.CreateAIAgent(name: VisionName, model: deploymentName, instructions: VisionInstructions);
ChatMessage message = new(ChatRole.User, [
new TextContent("What do you see in this image?"),
new UriContent("https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg", "image/jpeg")
]);
var thread = agent.GetNewThread();
await foreach (var update in agent.RunStreamingAsync(message, thread))
{
Console.WriteLine(update);
}
// Cleanup by agent name removes the agent version created.
agentsClient.DeleteAgent(agent.Name);
@@ -0,0 +1,52 @@
# Using Images with AI Agents
This sample demonstrates how to use image multi-modality with an AI agent. It shows how to create a vision-enabled agent that can analyze and describe images using Azure OpenAI.
## What this sample demonstrates
- Creating a persistent AI agent with vision capabilities
- Sending both text and image content to an agent in a single message
- Using `UriContent` to Uri referenced images
- Processing multimodal input (text + image) with an AI agent
## Key features
- **Vision Agent**: Creates an agent specifically instructed to analyze images
- **Multimodal Input**: Combines text questions with image uri in a single message
- **Azure OpenAI Integration**: Uses AzureOpenAI LLM agents
## Prerequisites
Before running this sample, ensure you have:
1. An Azure OpenAI project set up
2. A compatible model deployment (e.g., gpt-4o)
3. Azure CLI installed and authenticated
## Environment Variables
Set the following environment variables:
```powershell
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" # Replace with your Azure OpenAI endpoint
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o" # Replace with your model deployment name (optional, defaults to gpt-4o)
```
## Run the sample
Navigate to the sample directory and run:
```powershell
cd Agent_Step11_UsingImages
dotnet run
```
## Expected behavior
The sample will:
1. Create a vision-enabled agent named "VisionAgent"
2. Send a message containing both text ("What do you see in this image?") and a Uri image of a green walk
3. The agent will analyze the image and provide a description
4. Clean up resources by deleting the thread and agent
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>3afc9b74-af74-4d8e-ae96-fa1c511d11ac</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Agents" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,47 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows how to create and use an Azure Foundry Agents AI agent as a function tool.
using System.ComponentModel;
using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
const string WeatherInstructions = "You answer questions about the weather.";
const string WeatherName = "WeatherAgent";
const string MainInstructions = "You are a helpful assistant who responds in French.";
const string MainName = "MainAgent";
[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
=> $"The weather in {location} is cloudy with a high of 15°C.";
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
// Create the weather agent with function tools.
var weatherTool = AIFunctionFactory.Create(GetWeather);
AIAgent weatherAgent = agentsClient.CreateAIAgent(
name: WeatherName,
model: deploymentName,
instructions: WeatherInstructions,
tools: [weatherTool]);
// Create the main agent, and provide the weather agent as a function tool.
AIAgent agent = agentsClient.CreateAIAgent(
name: MainName,
model: deploymentName,
instructions: MainInstructions,
tools: [weatherAgent.AsAIFunction()]);
// Invoke the agent and output the text result.
AgentThread thread = agent.GetNewThread();
Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?", thread));
// Cleanup by agent name removes the agent versions created.
agentsClient.DeleteAgent(agent.Name);
agentsClient.DeleteAgent(weatherAgent.Name);
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Azure.AI.Agents" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,222 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows multiple middleware layers working together with Azure Foundry Agents:
// agent run (PII filtering and guardrails),
// function invocation (logging and result overrides), and human-in-the-loop
// approval workflows for sensitive function calls.
using System.ComponentModel;
using System.Text.RegularExpressions;
using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
// Get Azure AI Foundry configuration from environment variables
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o";
const string AssistantInstructions = "You are an AI assistant that helps people find information.";
const string AssistantName = "InformationAssistant";
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
=> $"The weather in {location} is cloudy with a high of 15°C.";
[Description("The current datetime offset.")]
static string GetDateTime()
=> DateTimeOffset.Now.ToString();
var dateTimeTool = AIFunctionFactory.Create(GetDateTime, name: nameof(GetDateTime));
var getWeatherTool = AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather));
// Define the agent you want to create. (Prompt Agent in this case)
AIAgent originalAgent = agentsClient.CreateAIAgent(
name: AssistantName,
model: deploymentName,
instructions: AssistantInstructions,
tools: [getWeatherTool, dateTimeTool]);
// Adding middleware to the agent level
var middlewareEnabledAgent = originalAgent
.AsBuilder()
.Use(FunctionCallMiddleware)
.Use(FunctionCallOverrideWeather)
.Use(PIIMiddleware, null)
.Use(GuardrailMiddleware, null)
.Build();
var thread = middlewareEnabledAgent.GetNewThread();
Console.WriteLine("\n\n=== Example 1: Wording Guardrail ===");
var guardRailedResponse = await middlewareEnabledAgent.RunAsync("Tell me something harmful.");
Console.WriteLine($"Guard railed response: {guardRailedResponse}");
Console.WriteLine("\n\n=== Example 2: PII detection ===");
var piiResponse = await middlewareEnabledAgent.RunAsync("My name is John Doe, call me at 123-456-7890 or email me at john@something.com");
Console.WriteLine($"Pii filtered response: {piiResponse}");
Console.WriteLine("\n\n=== Example 3: Agent function middleware ===");
// Agent function middleware support is limited to agents that wraps a upstream ChatClientAgent or derived from it.
var functionCallResponse = await middlewareEnabledAgent.RunAsync("What's the current time and the weather in Seattle?", thread);
Console.WriteLine($"Function calling response: {functionCallResponse}");
// Special per-request middleware agent.
Console.WriteLine("\n\n=== Example 4: Middleware with human in the loop function approval ===");
AIAgent humamInTheLoopAgent = agentsClient.CreateAIAgent(
name: "HumanInTheLoopAgent",
model: deploymentName,
instructions: "You are an Human in the loop testing AI assistant that helps people find information.",
// Adding a function with approval required
tools: [new ApprovalRequiredAIFunction(AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather)))]);
// Using the ConsolePromptingApprovalMiddleware for a specific request to handle user approval during function calls.
var response = await humamInTheLoopAgent
.AsBuilder()
.Use(ConsolePromptingApprovalMiddleware, null)
.Build()
.RunAsync("What's the current time and the weather in Seattle?");
Console.WriteLine($"HumamInTheLoopAgent agent middleware response: {response}");
// Function invocation middleware that logs before and after function calls.
async ValueTask<object?> FunctionCallMiddleware(AIAgent agent, FunctionInvocationContext context, Func<FunctionInvocationContext, CancellationToken, ValueTask<object?>> next, CancellationToken cancellationToken)
{
Console.WriteLine($"Function Name: {context!.Function.Name} - Middleware 1 Pre-Invoke");
var result = await next(context, cancellationToken);
Console.WriteLine($"Function Name: {context!.Function.Name} - Middleware 1 Post-Invoke");
return result;
}
// Function invocation middleware that overrides the result of the GetWeather function.
async ValueTask<object?> FunctionCallOverrideWeather(AIAgent agent, FunctionInvocationContext context, Func<FunctionInvocationContext, CancellationToken, ValueTask<object?>> next, CancellationToken cancellationToken)
{
Console.WriteLine($"Function Name: {context!.Function.Name} - Middleware 2 Pre-Invoke");
var result = await next(context, cancellationToken);
if (context.Function.Name == nameof(GetWeather))
{
// Override the result of the GetWeather function
result = "The weather is sunny with a high of 25°C.";
}
Console.WriteLine($"Function Name: {context!.Function.Name} - Middleware 2 Post-Invoke");
return result;
}
// This middleware redacts PII information from input and output messages.
async Task<AgentRunResponse> PIIMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
{
// Redact PII information from input messages
var filteredMessages = FilterMessages(messages);
Console.WriteLine("Pii Middleware - Filtered Messages Pre-Run");
var response = await innerAgent.RunAsync(filteredMessages, thread, options, cancellationToken).ConfigureAwait(false);
// Redact PII information from output messages
response.Messages = FilterMessages(response.Messages);
Console.WriteLine("Pii Middleware - Filtered Messages Post-Run");
return response;
static IList<ChatMessage> FilterMessages(IEnumerable<ChatMessage> messages)
{
return messages.Select(m => new ChatMessage(m.Role, FilterPii(m.Text))).ToList();
}
static string FilterPii(string content)
{
// Regex patterns for PII detection (simplified for demonstration)
Regex[] piiPatterns = [
new(@"\b\d{3}-\d{3}-\d{4}\b", RegexOptions.Compiled), // Phone number (e.g., 123-456-7890)
new(@"\b[\w\.-]+@[\w\.-]+\.\w+\b", RegexOptions.Compiled), // Email address
new(@"\b[A-Z][a-z]+\s[A-Z][a-z]+\b", RegexOptions.Compiled) // Full name (e.g., John Doe)
];
foreach (var pattern in piiPatterns)
{
content = pattern.Replace(content, "[REDACTED: PII]");
}
return content;
}
}
// This middleware enforces guardrails by redacting certain keywords from input and output messages.
async Task<AgentRunResponse> GuardrailMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
{
// Redact keywords from input messages
var filteredMessages = FilterMessages(messages);
Console.WriteLine("Guardrail Middleware - Filtered messages Pre-Run");
// Proceed with the agent run
var response = await innerAgent.RunAsync(filteredMessages, thread, options, cancellationToken);
// Redact keywords from output messages
response.Messages = FilterMessages(response.Messages);
Console.WriteLine("Guardrail Middleware - Filtered messages Post-Run");
return response;
List<ChatMessage> FilterMessages(IEnumerable<ChatMessage> messages)
{
return messages.Select(m => new ChatMessage(m.Role, FilterContent(m.Text))).ToList();
}
static string FilterContent(string content)
{
foreach (var keyword in new[] { "harmful", "illegal", "violence" })
{
if (content.Contains(keyword, StringComparison.OrdinalIgnoreCase))
{
return "[REDACTED: Forbidden content]";
}
}
return content;
}
}
// This middleware handles Human in the loop console interaction for any user approval required during function calling.
async Task<AgentRunResponse> ConsolePromptingApprovalMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
{
var response = await innerAgent.RunAsync(messages, thread, options, cancellationToken);
var userInputRequests = response.UserInputRequests.ToList();
while (userInputRequests.Count > 0)
{
// Ask the user to approve each function call request.
// For simplicity, we are assuming here that only function approval requests are being made.
// Pass the user input responses back to the agent for further processing.
response.Messages = userInputRequests
.OfType<FunctionApprovalRequestContent>()
.Select(functionApprovalRequest =>
{
Console.WriteLine($"The agent would like to invoke the following function, please reply Y to approve: Name {functionApprovalRequest.FunctionCall.Name}");
return new ChatMessage(ChatRole.User, [functionApprovalRequest.CreateResponse(Console.ReadLine()?.Equals("Y", StringComparison.OrdinalIgnoreCase) ?? false)]);
})
.ToList();
response = await innerAgent.RunAsync(response.Messages, thread, options, cancellationToken);
userInputRequests = response.UserInputRequests.ToList();
}
return response;
}
// Cleanup by agent name removes the agent version created.
agentsClient.DeleteAgent(middlewareEnabledAgent.Name);
@@ -0,0 +1,41 @@
# Agent Middleware
This sample demonstrates how to add middleware to intercept:
- Chat client calls (global and per‑request)
- Agent runs (guardrails and PII filtering)
- Function calling (logging/override)
## What This Sample Shows
1. Azure OpenAI integration via `AzureOpenAIClient` and `AzureCliCredential`
2. Chat client middleware using `ChatClientBuilder.Use(...)`
3. Agent run middleware (PII redaction and wording guardrails)
4. Function invocation middleware (logging and overriding a tool result)
5. Per‑request chat client middleware
6. Per‑request function pipeline with approval
7. Combining agent‑level and per‑request middleware
## Function Invocation Middleware
Not all agents support function invocation middleware.
Attempting to use function middleware on agents that do not wrap a ChatClientAgent or derives from it will throw an InvalidOperationException.
## Prerequisites
1. Environment variables:
- `AZURE_OPENAI_ENDPOINT`: Your Azure OpenAI endpoint
- `AZURE_OPENAI_DEPLOYMENT_NAME`: Chat deployment name (optional; defaults to `gpt-4o`)
2. Sign in with Azure CLI (PowerShell):
```powershell
az login
```
## Running the Sample
Use PowerShell:
```powershell
cd dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware
dotnet run
```
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);CA1812</NoWarn>
<RootNamespace>Agent_Step15_Plugins</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Azure.AI.Agents" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,139 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows how to use plugins with an AI agent. Plugin classes can
// depend on other services that need to be injected. In this sample, the
// AgentPlugin class uses the WeatherProvider and CurrentTimeProvider classes
// to get weather and current time information. Both services are registered
// in the service collection and injected into the plugin.
// Plugin classes may have many methods, but only some are intended to be used
// as AI functions. The AsAITools method of the plugin class shows how to specify
// which methods should be exposed to the AI agent.
using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
const string AssistantInstructions = "You are a helpful assistant that helps people find information.";
const string AssistantName = "PluginAssistant";
// Create a service collection to hold the agent plugin and its dependencies.
ServiceCollection services = new();
services.AddSingleton<WeatherProvider>();
services.AddSingleton<CurrentTimeProvider>();
services.AddSingleton<AgentPlugin>(); // The plugin depends on WeatherProvider and CurrentTimeProvider registered above.
IServiceProvider serviceProvider = services.BuildServiceProvider();
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
// Define the agent with plugin tools
// Define the agent you want to create. (Prompt Agent in this case)
AIAgent agent = agentsClient.CreateAIAgent(
name: AssistantName,
model: deploymentName,
instructions: AssistantInstructions,
tools: serviceProvider.GetRequiredService<AgentPlugin>().AsAITools().ToList(),
services: serviceProvider);
// Invoke the agent and output the text result.
AgentThread thread = agent.GetNewThread();
Console.WriteLine(await agent.RunAsync("Tell me current time and weather in Seattle.", thread));
// Cleanup by agent name removes the agent version created.
agentsClient.DeleteAgent(agent.Name);
/// <summary>
/// The agent plugin that provides weather and current time information.
/// </summary>
/// <param name="weatherProvider">The weather provider to get weather information.</param>
internal sealed class AgentPlugin(WeatherProvider weatherProvider)
{
/// <summary>
/// Gets the weather information for the specified location.
/// </summary>
/// <remarks>
/// This method demonstrates how to use the dependency that was injected into the plugin class.
/// </remarks>
/// <param name="location">The location to get the weather for.</param>
/// <returns>The weather information for the specified location.</returns>
public string GetWeather(string location)
{
return weatherProvider.GetWeather(location);
}
/// <summary>
/// Gets the current date and time for the specified location.
/// </summary>
/// <remarks>
/// This method demonstrates how to resolve a dependency using the service provider passed to the method.
/// </remarks>
/// <param name="sp">The service provider to resolve the <see cref="CurrentTimeProvider"/>.</param>
/// <param name="location">The location to get the current time for.</param>
/// <returns>The current date and time as a <see cref="DateTimeOffset"/>.</returns>
public DateTimeOffset GetCurrentTime(IServiceProvider sp, string location)
{
// Resolve the CurrentTimeProvider from the service provider
var currentTimeProvider = sp.GetRequiredService<CurrentTimeProvider>();
return currentTimeProvider.GetCurrentTime(location);
}
/// <summary>
/// Returns the functions provided by this plugin.
/// </summary>
/// <remarks>
/// In real world scenarios, a class may have many methods and only a subset of them may be intended to be exposed as AI functions.
/// This method demonstrates how to explicitly specify which methods should be exposed to the AI agent.
/// </remarks>
/// <returns>The functions provided by this plugin.</returns>
public IEnumerable<AITool> AsAITools()
{
yield return AIFunctionFactory.Create(this.GetWeather);
yield return AIFunctionFactory.Create(this.GetCurrentTime);
}
}
/// <summary>
/// The weather provider that returns weather information.
/// </summary>
internal sealed class WeatherProvider
{
/// <summary>
/// Gets the weather information for the specified location.
/// </summary>
/// <remarks>
/// The weather information is hardcoded for demonstration purposes.
/// In a real application, this could call a weather API to get actual weather data.
/// </remarks>
/// <param name="location">The location to get the weather for.</param>
/// <returns>The weather information for the specified location.</returns>
public string GetWeather(string location)
{
return $"The weather in {location} is cloudy with a high of 15°C.";
}
}
/// <summary>
/// Provides the current date and time.
/// </summary>
/// <remarks>
/// This class returns the current date and time using the system's clock.
/// </remarks>
internal sealed class CurrentTimeProvider
{
/// <summary>
/// Gets the current date and time.
/// </summary>
/// <param name="location">The location to get the current time for (not used in this implementation).</param>
/// <returns>The current date and time as a <see cref="DateTimeOffset"/>.</returns>
public DateTimeOffset GetCurrentTime(string location)
{
return DateTimeOffset.Now;
}
}
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(ProjectsTargetFrameworks)</TargetFrameworks>
@@ -0,0 +1,831 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.AzureAI;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
using OpenAI;
using OpenAI.Responses;
#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
namespace Azure.AI.Agents;
/// <summary>
/// Provides extension methods for <see cref="AgentsClient"/>.
/// </summary>
public static class AgentsClientExtensions
{
/// <summary>
/// Retrieves an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AgentsClient"/>.
/// </summary>
/// <param name="agentsClient">The <see cref="AgentsClient"/> to create the <see cref="ChatClientAgent"/> with. Cannot be <see langword="null"/>.</param>
/// <param name="name">The name of the server side agent to create a <see cref="ChatClientAgent"/> for. Cannot be <see langword="null"/> or whitespace.</param>
/// <param name="tools">The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations based on the latest version of the named Azure AI Agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/> or <paramref name="name"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="name"/> is empty or whitespace, or when the agent with the specified name was not found.</exception>
/// <exception cref="InvalidOperationException">The agent with the specified name was not found.</exception>
/// <remarks>When using prompt agent definitions with tools the parameter <paramref name="tools"/> needs to be provided.</remarks>
public static ChatClientAgent GetAIAgent(
this AgentsClient agentsClient,
string name,
IList<AITool>? tools = null,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
Throw.IfNullOrWhitespace(name);
var agentRecord = agentsClient.GetAgent(name, cancellationToken).Value
?? throw new InvalidOperationException($"Agent with name '{name}' not found.");
return GetAIAgent(
agentsClient,
agentRecord,
tools,
clientFactory,
openAIClientOptions,
services,
cancellationToken);
}
/// <summary>
/// Asynchronously retrieves an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AgentsClient"/>.
/// </summary>
/// <param name="agentsClient">The <see cref="AgentsClient"/> to create the <see cref="ChatClientAgent"/> with. Cannot be <see langword="null"/>.</param>
/// <param name="name">The name of the server side agent to create a <see cref="ChatClientAgent"/> for. Cannot be <see langword="null"/> or whitespace.</param>
/// <param name="tools">The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations based on the latest version of the named Azure AI Agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/> or <paramref name="name"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="name"/> is empty or whitespace, or when the agent with the specified name was not found.</exception>
/// <exception cref="InvalidOperationException">The agent with the specified name was not found.</exception>
/// <remarks>When using prompt agent definitions with tools the parameter <paramref name="tools"/> needs to be provided.</remarks>
public static async Task<ChatClientAgent> GetAIAgentAsync(
this AgentsClient agentsClient,
string name,
IList<AITool>? tools = null,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
Throw.IfNullOrWhitespace(name);
var agentRecord = (await agentsClient.GetAgentAsync(name, cancellationToken).ConfigureAwait(false)).Value
?? throw new InvalidOperationException($"Agent with name '{name}' not found.");
return GetAIAgent(
agentsClient,
agentRecord,
tools,
clientFactory,
openAIClientOptions,
services,
cancellationToken);
}
/// <summary>
/// Gets a runnable agent instance from the provided agent record.
/// </summary>
/// <param name="agentsClient">The client used to interact with Azure AI Agents. Cannot be <see langword="null"/>.</param>
/// <param name="agentRecord">The agent record to be converted. The latest version will be used. Cannot be <see langword="null"/>.</param>
/// <param name="tools">The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations based on the latest version of the Azure AI Agent.</returns>
/// <remarks>When using prompt agent definitions with tools the parameter <paramref name="tools"/> needs to be provided.</remarks>
public static ChatClientAgent GetAIAgent(
this AgentsClient agentsClient,
AgentRecord agentRecord,
IList<AITool>? tools = null,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
Throw.IfNull(agentRecord);
return GetAIAgent(
agentsClient,
agentRecord.Versions.Latest,
tools,
clientFactory,
openAIClientOptions,
services,
cancellationToken);
}
/// <summary>
/// Gets a runnable agent instance from a <see cref="AgentVersion"/> containing metadata about an Azure AI Agent.
/// </summary>
/// <param name="agentsClient">The client used to interact with Azure AI Agents. Cannot be <see langword="null"/>.</param>
/// <param name="agentVersion">The agent version to be converted. Cannot be <see langword="null"/>.</param>
/// <param name="tools">The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations based on the provided version of the Azure AI Agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/> or <paramref name="agentVersion"/> is <see langword="null"/>.</exception>
/// <remarks>When using prompt agent definitions with tools the parameter <paramref name="tools"/> needs to be provided.</remarks>
public static ChatClientAgent GetAIAgent(
this AgentsClient agentsClient,
AgentVersion agentVersion,
IList<AITool>? tools = null,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
Throw.IfNull(agentVersion);
ValidateUsingToolsParameter(agentVersion, tools);
return CreateChatClientAgent(
agentsClient,
agentVersion,
tools,
clientFactory,
openAIClientOptions,
requireInvocableTools: true,
services);
}
/// <summary>
/// Creates a new Prompt AI Agent using the provided <see cref="AgentsClient"/> and options.
/// </summary>
/// <param name="agentsClient">The client used to manage and interact with AI agents. Cannot be <see langword="null"/>.</param>
/// <param name="options">The options for creating the agent. Cannot be <see langword="null"/>.</param>
/// <param name="clientFactory">A factory function to customize the creation of the chat client used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to cancel the operation if needed.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the newly created agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
public static ChatClientAgent GetAIAgent(
this AgentsClient agentsClient,
ChatClientAgentOptions options,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
Throw.IfNull(options);
if (string.IsNullOrWhiteSpace(options.Name))
{
throw new ArgumentException("Agent name must be provided in the options.Name property", nameof(options));
}
var agentRecord = agentsClient.GetAgent(options.Name, cancellationToken).Value
?? throw new InvalidOperationException($"Agent with name '{options.Name}' not found.");
var agentVersion = agentRecord.Versions.Latest;
var agentOptions = CreateChatClientAgentOptions(agentVersion, options, requireInvocableTools: true);
return CreateChatClientAgent(
agentsClient,
agentVersion,
agentOptions,
clientFactory,
openAIClientOptions,
requireInvocableTools: true,
services);
}
/// <summary>
/// Creates a new Prompt AI Agent using the provided <see cref="AgentsClient"/> and options.
/// </summary>
/// <param name="agentsClient">The client used to manage and interact with AI agents. Cannot be <see langword="null"/>.</param>
/// <param name="options">The options for creating the agent. Cannot be <see langword="null"/>.</param>
/// <param name="clientFactory">A factory function to customize the creation of the chat client used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to cancel the operation if needed.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the newly created agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
public static async Task<ChatClientAgent> GetAIAgentAsync(
this AgentsClient agentsClient,
ChatClientAgentOptions options,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
Throw.IfNull(options);
if (string.IsNullOrWhiteSpace(options.Name))
{
throw new ArgumentException("Agent name must be provided in the options.Name property", nameof(options));
}
var agentRecord = (await agentsClient.GetAgentAsync(options.Name, cancellationToken).ConfigureAwait(false)).Value
?? throw new InvalidOperationException($"Agent with name '{options.Name}' not found.");
var agentVersion = agentRecord.Versions.Latest;
var agentOptions = CreateChatClientAgentOptions(agentVersion, options, requireInvocableTools: true);
return CreateChatClientAgent(
agentsClient,
agentVersion,
agentOptions,
clientFactory,
openAIClientOptions,
requireInvocableTools: true,
services);
}
/// <summary>
/// Creates a new Prompt AI agent using the specified configuration parameters.
/// </summary>
/// <param name="agentsClient">The client used to manage and interact with AI agents. Cannot be <see langword="null"/>.</param>
/// <param name="name">The name for the agent.</param>
/// <param name="model">The name of the model to use for the agent. Cannot be <see langword="null"/> or whitespace.</param>
/// <param name="instructions">The instructions that guide the agent's behavior. Cannot be <see langword="null"/> or whitespace.</param>
/// <param name="tools">The tools to use when interacting with the agent, this is required when using prompt agent definitions with tools.</param>
/// <param name="creationOptions">Settings that control the creation of the agent.</param>
/// <param name="clientFactory">A factory function to customize the creation of the chat client used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the newly created agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/>, <paramref name="model"/>, or <paramref name="instructions"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="model"/> or <paramref name="instructions"/> is empty or whitespace.</exception>
/// <remarks>When using prompt agent definitions with tools the parameter <paramref name="tools"/> needs to be provided.</remarks>
public static ChatClientAgent CreateAIAgent(
this AgentsClient agentsClient,
string name,
string model,
string instructions,
IList<AITool>? tools = null,
AgentVersionCreationOptions? creationOptions = null,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
Throw.IfNullOrWhitespace(name);
Throw.IfNullOrWhitespace(model);
Throw.IfNullOrWhitespace(instructions);
return CreateAIAgent(
agentsClient,
name,
tools,
new PromptAgentDefinition(model) { Instructions = instructions },
creationOptions,
clientFactory,
openAIClientOptions,
requireInvocableTools: true,
services,
cancellationToken);
}
/// <summary>
/// Creates a new Prompt AI agent using the specified configuration parameters.
/// </summary>
/// <param name="agentsClient">The client used to manage and interact with AI agents. Cannot be <see langword="null"/>.</param>
/// <param name="name">The name for the agent.</param>
/// <param name="model">The name of the model to use for the agent. Cannot be <see langword="null"/> or whitespace.</param>
/// <param name="instructions">The instructions that guide the agent's behavior. Cannot be <see langword="null"/> or whitespace.</param>
/// <param name="tools">The tools to use when interacting with the agent, this is required when using prompt agent definitions with tools.</param>
/// <param name="creationOptions">Settings that control the creation of the agent.</param>
/// <param name="clientFactory">A factory function to customize the creation of the chat client used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the newly created agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/>, <paramref name="model"/>, or <paramref name="instructions"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="model"/> or <paramref name="instructions"/> is empty or whitespace.</exception>
/// <remarks>When using prompt agent definitions with tools the parameter <paramref name="tools"/> needs to be provided.</remarks>
public static Task<ChatClientAgent> CreateAIAgentAsync(
this AgentsClient agentsClient,
string name,
string model,
string instructions,
IList<AITool>? tools = null,
AgentVersionCreationOptions? creationOptions = null,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
Throw.IfNullOrWhitespace(name);
Throw.IfNullOrWhitespace(model);
Throw.IfNullOrWhitespace(instructions);
return CreateAIAgentAsync(
agentsClient,
name,
tools,
new PromptAgentDefinition(model) { Instructions = instructions },
creationOptions,
clientFactory,
openAIClientOptions,
requireInvocableTools: true,
services,
cancellationToken);
}
/// <summary>
/// Creates a new Prompt AI Agent using the provided <see cref="AgentsClient"/> and options.
/// </summary>
/// <param name="agentsClient">The client used to manage and interact with AI agents. Cannot be <see langword="null"/>.</param>
/// <param name="model">The name of the model to use for the agent. Cannot be <see langword="null"/> or whitespace.</param>
/// <param name="options">The options for creating the agent. Cannot be <see langword="null"/>.</param>
/// <param name="clientFactory">A factory function to customize the creation of the chat client used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to cancel the operation if needed.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the newly created agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="model"/> is empty or whitespace, or when the agent name is not provided in the options.</exception>
public static ChatClientAgent CreateAIAgent(
this AgentsClient agentsClient,
string model,
ChatClientAgentOptions options,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
Throw.IfNull(options);
Throw.IfNullOrWhitespace(model);
const bool RequireInvocableTools = true;
if (string.IsNullOrWhiteSpace(options.Name))
{
throw new ArgumentException("Agent name must be provided in the options.Name property", nameof(options));
}
PromptAgentDefinition agentDefinition = new(model)
{
Instructions = options.Instructions,
};
ApplyToolsToAgentDefinition(agentDefinition, options.ChatOptions?.Tools, RequireInvocableTools);
AgentVersionCreationOptions? versionCreationOptions = null;
if (!string.IsNullOrWhiteSpace(options.Description))
{
(versionCreationOptions ??= new()).Description = options.Description;
}
AgentVersion agentVersion = agentsClient.CreateAgentVersion(options.Name, agentDefinition, versionCreationOptions, cancellationToken).Value;
var agentOptions = CreateChatClientAgentOptions(agentVersion, options, RequireInvocableTools);
return CreateChatClientAgent(
agentsClient,
agentVersion,
agentOptions,
clientFactory,
openAIClientOptions,
RequireInvocableTools,
services);
}
/// <summary>
/// Creates a new Prompt AI Agent using the provided <see cref="AgentsClient"/> and options.
/// </summary>
/// <param name="agentsClient">The client used to manage and interact with AI agents. Cannot be <see langword="null"/>.</param>
/// <param name="model">The name of the model to use for the agent. Cannot be <see langword="null"/> or whitespace.</param>
/// <param name="options">The options for creating the agent. Cannot be <see langword="null"/>.</param>
/// <param name="clientFactory">A factory function to customize the creation of the chat client used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to cancel the operation if needed.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the newly created agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="model"/> is empty or whitespace, or when the agent name is not provided in the options.</exception>
public static async Task<ChatClientAgent> CreateAIAgentAsync(
this AgentsClient agentsClient,
string model,
ChatClientAgentOptions options,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
Throw.IfNull(options);
Throw.IfNullOrWhitespace(model);
const bool RequireInvocableTools = true;
if (string.IsNullOrWhiteSpace(options.Name))
{
throw new ArgumentException("Agent name must be provided in the options.Name property", nameof(options));
}
PromptAgentDefinition agentDefinition = new(model)
{
Instructions = options.Instructions,
};
ApplyToolsToAgentDefinition(agentDefinition, options.ChatOptions?.Tools, RequireInvocableTools);
AgentVersionCreationOptions? versionCreationOptions = null;
if (!string.IsNullOrWhiteSpace(options.Description))
{
(versionCreationOptions ??= new()).Description = options.Description;
}
AgentVersion agentVersion = await agentsClient.CreateAgentVersionAsync(options.Name, agentDefinition, versionCreationOptions, cancellationToken).ConfigureAwait(false);
var agentOptions = CreateChatClientAgentOptions(agentVersion, options, RequireInvocableTools);
return CreateChatClientAgent(
agentsClient,
agentVersion,
agentOptions,
clientFactory,
openAIClientOptions,
RequireInvocableTools,
services);
}
/// <summary>
/// Creates a new AI agent using the specified agent definition and optional configuration parameters.
/// </summary>
/// <param name="agentsClient">The client used to manage and interact with AI agents. Cannot be <see langword="null"/>.</param>
/// <param name="name">The name for the agent.</param>
/// <param name="agentDefinition">The definition that specifies the configuration and behavior of the agent to create. Cannot be <see langword="null"/>.</param>
/// <param name="creationOptions">Settings that control the creation of the agent.</param>
/// <param name="clientFactory">A factory function to customize the creation of the chat client used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the newly created agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/> or <paramref name="agentDefinition"/> is <see langword="null"/>.</exception>
/// <remarks>
/// When using this extension method with a <see cref="PromptAgentDefinition"/> the tools are only declarative and not invocable.
/// Invocation of any in-process tools will need to be handled manually.
/// </remarks>
public static ChatClientAgent CreateAIAgent(
this AgentsClient agentsClient,
string name,
AgentDefinition agentDefinition,
AgentVersionCreationOptions? creationOptions = null,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
Throw.IfNullOrWhitespace(name);
Throw.IfNull(agentDefinition);
var tools = (agentDefinition as PromptAgentDefinition)?.Tools.Select(t => t.AsAITool()).ToList();
return CreateAIAgent(
agentsClient,
name,
tools,
agentDefinition,
creationOptions,
clientFactory,
openAIClientOptions,
requireInvocableTools: false,
services: null,
cancellationToken);
}
/// <summary>
/// Asynchronously creates a new AI agent using the specified agent definition and optional configuration
/// parameters.
/// </summary>
/// <param name="agentsClient">The client used to manage and interact with AI agents. Cannot be <see langword="null"/>.</param>
/// <param name="name">The name for the agent.</param>
/// <param name="agentDefinition">The definition that specifies the configuration and behavior of the agent to create. Cannot be <see langword="null"/>.</param>
/// <param name="agentVersionCreationOptions">Settings that control the creation of the agent.</param>
/// <param name="clientFactory">A factory function to customize the creation of the chat client used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the newly created agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/> or <paramref name="agentDefinition"/> is <see langword="null"/>.</exception>
/// <remarks>
/// When using this extension method with a <see cref="PromptAgentDefinition"/> the tools are only declarative and not invocable.
/// Invocation of any in-process tools will need to be handled manually.
/// </remarks>
public static Task<ChatClientAgent> CreateAIAgentAsync(
this AgentsClient agentsClient,
string name,
AgentDefinition agentDefinition,
AgentVersionCreationOptions? agentVersionCreationOptions = null,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
CancellationToken cancellationToken = default)
{
Throw.IfNullOrWhitespace(name);
Throw.IfNull(agentsClient);
Throw.IfNull(agentDefinition);
var tools = (agentDefinition as PromptAgentDefinition)?.Tools.Select(t => t.AsAITool()).ToList();
return CreateAIAgentAsync(
agentsClient,
name,
tools,
agentDefinition,
agentVersionCreationOptions,
clientFactory,
openAIClientOptions,
requireInvocableTools: false,
services: null,
cancellationToken);
}
#region Private
private static ChatClientAgent CreateAIAgent(
this AgentsClient agentsClient,
string name,
IList<AITool>? tools,
AgentDefinition agentDefinition,
AgentVersionCreationOptions? creationOptions,
Func<IChatClient, IChatClient>? clientFactory,
OpenAIClientOptions? openAIClientOptions,
bool requireInvocableTools,
IServiceProvider? services,
CancellationToken cancellationToken)
{
Throw.IfNull(agentsClient);
Throw.IfNullOrWhitespace(name);
Throw.IfNull(agentDefinition);
tools ??= (agentDefinition as PromptAgentDefinition)?.Tools.Select(t => t.AsAITool()).ToList();
ApplyToolsToAgentDefinition(agentDefinition, tools, requireInvocableTools);
AgentVersion agentVersion = agentsClient.CreateAgentVersion(name, agentDefinition, creationOptions, cancellationToken).Value;
return CreateChatClientAgent(
agentsClient,
agentVersion,
tools,
clientFactory,
openAIClientOptions,
requireInvocableTools,
services);
}
private static async Task<ChatClientAgent> CreateAIAgentAsync(
this AgentsClient agentsClient,
string name,
IList<AITool>? tools,
AgentDefinition agentDefinition,
AgentVersionCreationOptions? agentVersionCreationOptions,
Func<IChatClient, IChatClient>? clientFactory,
OpenAIClientOptions? openAIClientOptions,
bool requireInvocableTools,
IServiceProvider? services,
CancellationToken cancellationToken)
{
Throw.IfNullOrWhitespace(name);
Throw.IfNull(agentsClient);
Throw.IfNull(agentDefinition);
tools ??= (agentDefinition as PromptAgentDefinition)?.Tools.Select(t => t.AsAITool()).ToList();
ApplyToolsToAgentDefinition(agentDefinition, tools, requireInvocableTools);
AgentVersion agentVersion = await agentsClient.CreateAgentVersionAsync(name, agentDefinition, agentVersionCreationOptions, cancellationToken).ConfigureAwait(false);
return CreateChatClientAgent(
agentsClient,
agentVersion,
tools,
clientFactory,
openAIClientOptions,
requireInvocableTools,
services);
}
/// <summary>This method creates an <see cref="ChatClientAgent"/> with the specified ChatClientAgentOptions.</summary>
private static ChatClientAgent CreateChatClientAgent(
AgentsClient agentsClient,
AgentVersion agentVersion,
ChatClientAgentOptions agentOptions,
Func<IChatClient, IChatClient>? clientFactory,
OpenAIClientOptions? openAIClientOptions,
bool requireInvocableTools,
IServiceProvider? services)
{
IChatClient chatClient = new AzureAIAgentChatClient(agentsClient, agentVersion, agentOptions.ChatOptions, openAIClientOptions);
if (clientFactory is not null)
{
chatClient = clientFactory(chatClient);
}
return new ChatClientAgent(chatClient, agentOptions, services: services);
}
/// <summary>This method creates an <see cref="ChatClientAgent"/> with a auto-generated ChatClientAgentOptions from the specified configuration parameters.</summary>
private static ChatClientAgent CreateChatClientAgent(
AgentsClient agentsClient,
AgentVersion agentVersion,
IList<AITool>? tools,
Func<IChatClient, IChatClient>? clientFactory,
OpenAIClientOptions? openAIClientOptions,
bool requireInvocableTools,
IServiceProvider? services)
=> CreateChatClientAgent(
agentsClient,
agentVersion,
CreateChatClientAgentOptions(agentVersion, new ChatOptions() { Tools = tools }, requireInvocableTools),
clientFactory,
openAIClientOptions,
requireInvocableTools,
services);
/// <summary>
/// This method creates <see cref="ChatClientAgentOptions"/> for the specified <see cref="AgentVersion"/> and the provided tools.
/// </summary>
/// <param name="agentVersion">The agent version.</param>
/// <param name="chatOptions">The <see cref="ChatOptions"/> to use when interacting with the agent.</param>
/// <param name="requireInvocableTools">Indicates whether to enforce the presence of invocable tools when the AIAgent is created with an agent definition that uses them.</param>
/// <returns>The created <see cref="ChatClientAgentOptions"/>.</returns>
/// <exception cref="InvalidOperationException">Thrown when the agent definition requires in-process tools but none were provided.</exception>
/// <exception cref="InvalidOperationException">Thrown when the agent definition required tools were not provided.</exception>
/// <remarks>
/// This method rebuilds the agent options from the agent definition returned by the version and combine with the in-proc tools when provided
/// this ensures that all required tools are provided and the definition of the agent options are consistent with the agent definition coming from the server.
/// </remarks>
private static ChatClientAgentOptions CreateChatClientAgentOptions(AgentVersion agentVersion, ChatOptions? chatOptions, bool requireInvocableTools)
{
var agentDefinition = agentVersion.Definition;
List<AITool>? agentTools = null;
if (agentDefinition is PromptAgentDefinition { Tools: { Count: > 0 } definitionTools })
{
// Check if no tools were provided while the agent definition requires in-proc tools.
if (requireInvocableTools && chatOptions?.Tools is null or { Count: 0 } && definitionTools.Any(t => t is FunctionTool))
{
throw new ArgumentException("The agent definition in-process tools must be provided in the extension method tools parameter.");
}
// Agregate all missing tools for a single error message.
List<string>? missingTools = null;
// Check function tools
foreach (ResponseTool responseTool in definitionTools)
{
if (responseTool is FunctionTool functionTool)
{
// Check if a tool with the same type and name exists in the provided tools.
var matchingTool = chatOptions?.Tools?.FirstOrDefault(t =>
requireInvocableTools
? t is AIFunction tf && functionTool.FunctionName == tf.Name // When invocable tools are required, match only AIFunction.
: (t is AIFunctionDeclaration tfd && functionTool.FunctionName == tfd.Name) ? true // When not required, match AIFunctionDeclaration OR
: (t.GetService<FunctionTool>() is FunctionTool ft && functionTool.FunctionName == ft.FunctionName)); // Match a FunctionTool converted AsAITool.
if (matchingTool is null)
{
(missingTools ??= []).Add($"Function tool: {functionTool.FunctionName}");
}
else
{
(agentTools ??= []).Add(matchingTool!);
}
continue;
}
(agentTools ??= []).Add(responseTool.AsAITool());
}
if (missingTools is { Count: > 0 })
{
throw new InvalidOperationException($"The following prompt agent definition required tools were not provided: {string.Join(", ", missingTools)}");
}
}
var agentOptions = new ChatClientAgentOptions()
{
Id = agentVersion.Id,
Name = agentVersion.Name,
Description = agentVersion.Description,
};
if (agentDefinition is PromptAgentDefinition promptAgentDefinition)
{
agentOptions.ChatOptions ??= chatOptions?.Clone() ?? new();
agentOptions.Instructions = promptAgentDefinition.Instructions;
agentOptions.ChatOptions.Temperature = promptAgentDefinition.Temperature;
agentOptions.ChatOptions.TopP = promptAgentDefinition.TopP;
agentOptions.ChatOptions.Instructions = promptAgentDefinition.Instructions;
}
if (agentTools is { Count: > 0 })
{
agentOptions.ChatOptions ??= chatOptions?.Clone() ?? new();
agentOptions.ChatOptions.Tools = agentTools;
}
return agentOptions;
}
/// <summary>
/// Creates a new instance of <see cref="ChatClientAgentOptions"/> configured for the specified agent version and
/// optional base options.
/// </summary>
/// <param name="agentVersion">The agent version to use when configuring the chat client agent options.</param>
/// <param name="options">An optional <see cref="ChatClientAgentOptions"/> instance whose relevant properties will be copied to the
/// returned options. If <see langword="null"/>, only default values are used.</param>
/// <param name="requireInvocableTools">Specifies whether the returned options must include invocable tools. Set to <see langword="true"/> to require
/// invocable tools; otherwise, <see langword="false"/>.</param>
/// <returns>A <see cref="ChatClientAgentOptions"/> instance configured according to the specified parameters.</returns>
private static ChatClientAgentOptions CreateChatClientAgentOptions(AgentVersion agentVersion, ChatClientAgentOptions? options, bool requireInvocableTools)
{
var agentOptions = CreateChatClientAgentOptions(agentVersion, options?.ChatOptions, requireInvocableTools);
if (options is not null)
{
agentOptions.AIContextProviderFactory = options.AIContextProviderFactory;
agentOptions.ChatMessageStoreFactory = options.ChatMessageStoreFactory;
agentOptions.UseProvidedChatClientAsIs = options.UseProvidedChatClientAsIs;
}
return agentOptions;
}
/// <summary>For already created agent versions, retrieve the definition and validate the tools parameter.</summary>
/// <exception cref="ArgumentException"><see cref="PromptAgentDefinition.Tools"/> cannot be used. The <paramref name="tools"/> parameter should be used instead.</exception>
/// <remarks>
/// Because <see cref="PromptAgentDefinition.Tools"/> doesn't support in-proc tools (only declarative/definitions),
/// the <paramref name="tools"/> parameter needs to be the single source of truth for tools, and must be provided when using tools.
/// </remarks>
private static void ValidateUsingToolsParameter(AgentVersion agentVersion, IList<AITool>? tools)
{
if (agentVersion.Definition is PromptAgentDefinition { Tools.Count: > 0 } && tools is null or { Count: 0 })
{
throw new ArgumentException("When retrieving prompt agents with tools the tools parameter needs to be provided with the necessary tools.", nameof(tools));
}
}
/// <summary>
/// Adds the specified AI tools to a prompt agent definition, ensuring that all tools are compatible and, if required, invocable.
/// </summary>
/// <remarks>This method ensures that only compatible and properly constructed tools are added to the agent definition.
/// When <paramref name="requireInvocableTools"/> is <see langword="true"/>, all tools must be
/// invocable AIFunctions, which can be created using AIFunctionFactory.Create. Tools are converted to ResponseTool
/// instances before being added.</remarks>
/// <param name="agentDefinition">The agent definition to which the tools will be applied. Must be a PromptAgentDefinition to support tools.</param>
/// <param name="tools">A list of AI tools to add to the agent definition. If null or empty, no tools are added.</param>
/// <param name="requireInvocableTools">Indicates whether all provided tools must be invocable AI functions. If set to <see langword="true"/>, only
/// invocable AIFunctions are accepted.</param>
/// <exception cref="ArgumentException">Thrown if <paramref name="agentDefinition"/> is not a <see cref="PromptAgentDefinition"/>.</exception>
/// <exception cref="InvalidOperationException">Thrown if <paramref name="requireInvocableTools"/> is <see langword="true"/> and a tool is an
/// <see cref="AIFunctionDeclaration"/> that is not invocable, or if a tool cannot be converted to a <see cref="ResponseTool"/>.</exception>
private static void ApplyToolsToAgentDefinition(AgentDefinition agentDefinition, IList<AITool>? tools, bool requireInvocableTools)
{
if (tools is { Count: > 0 })
{
if (agentDefinition is not PromptAgentDefinition promptAgentDefinition)
{
throw new ArgumentException("Only prompt agent definitions support tools.", nameof(agentDefinition));
}
foreach (var tool in tools)
{
// Ensure that any AIFunctions provided are In-Proc, not just the declarations.
if (requireInvocableTools && tool is not AIFunction && (
tool.GetService<FunctionTool>() is not null // Declarative FunctionTool converted as AsAITool()
|| tool is AIFunctionDeclaration)) // AIFunctionDeclaration type
{
throw new InvalidOperationException("When providing functions, they need to be invokable AIFunctions. AIFunctions can be created correctly using AIFunctionFactory.Create");
}
promptAgentDefinition.Tools.Add(
// If this is a converted ResponseTool as AITool, we can directly retrieve the ResponseTool instance from GetService.
tool.GetService<ResponseTool>()
// Otherwise we should be able to convert existing MEAI Tool abstractions into OpenAI ResponseTools
?? tool.AsOpenAIResponseTool()
?? throw new InvalidOperationException("The provided AITool could not be converted to a ResponseTool, ensure that the AITool was created using responseTool.AsAITool() extension."));
}
}
}
#endregion
}
@@ -0,0 +1,148 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ClientModel.Primitives;
using System.Runtime.CompilerServices;
using System.Text;
using Azure.AI.Agents;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
using OpenAI;
using OpenAI.Responses;
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
namespace Microsoft.Agents.AI.AzureAI;
/// <summary>
/// Provides a chat client implementation that integrates with Azure AI Agents, enabling chat interactions using
/// Azure-specific agent capabilities.
/// </summary>
internal sealed class AzureAIAgentChatClient : DelegatingChatClient
{
private readonly ChatClientMetadata? _metadata;
private readonly AgentsClient _agentsClient;
private readonly AgentVersion _agentVersion;
private readonly ChatOptions? _chatOptions;
/// <summary>
/// The usage of a no-op model is a necessary change to avoid OpenAIClients to throw exceptions when
/// used with Azure AI Agents as the model used is now defined at the agent creation time.
/// </summary>
private const string NoOpModel = "no-op";
/// <summary>
/// Initializes a new instance of the <see cref="AzureAIAgentChatClient"/> class.
/// </summary>
/// <param name="agentsClient">An instance of <see cref="AgentsClient"/> to interact with Azure AI Agents services.</param>
/// <param name="agentRecord">An instance of <see cref="AgentRecord"/> representing the specific agent to use.</param>
/// <param name="chatOptions">An instance of <see cref="ChatOptions"/> representing the options on how the agent was predefined</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <remarks>
/// The <see cref="IChatClient"/> provided should be decorated with a <see cref="AzureAIAgentChatClient"/> for proper functionality.
/// </remarks>
internal AzureAIAgentChatClient(AgentsClient agentsClient, AgentRecord agentRecord, ChatOptions? chatOptions, OpenAIClientOptions? openAIClientOptions = null)
: this(agentsClient, Throw.IfNull(agentRecord).Versions.Latest, chatOptions, openAIClientOptions)
{
}
internal AzureAIAgentChatClient(AgentsClient agentsClient, AgentVersion agentVersion, ChatOptions? chatOptions, OpenAIClientOptions? openAIClientOptions = null)
: base(agentsClient
.GetOpenAIClient(openAIClientOptions)
.GetOpenAIResponseClient((agentVersion.Definition as PromptAgentDefinition)?.Model ?? NoOpModel)
.AsIChatClient())
{
this._agentsClient = Throw.IfNull(agentsClient);
this._agentVersion = Throw.IfNull(agentVersion);
this._metadata = new ChatClientMetadata("azure.ai.agents");
this._chatOptions = chatOptions;
}
/// <inheritdoc/>
public override object? GetService(Type serviceType, object? serviceKey = null)
{
return (serviceKey is null && serviceType == typeof(ChatClientMetadata))
? this._metadata
: (serviceKey is null && serviceType == typeof(AgentsClient))
? this._agentsClient
: (serviceKey is null && serviceType == typeof(AgentVersion))
? this._agentVersion
: base.GetService(serviceType, serviceKey);
}
/// <inheritdoc/>
public override async Task<ChatResponse> GetResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
{
var conversationId = await this.GetOrCreateConversationAsync(options, cancellationToken).ConfigureAwait(false);
var conversationChatOptions = this.GetConversationEnabledChatOptions(options, conversationId);
return await base.GetResponseAsync(messages, conversationChatOptions, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc/>
public async override IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var conversation = await this.GetOrCreateConversationAsync(options, cancellationToken).ConfigureAwait(false);
var conversationOptions = this.GetConversationEnabledChatOptions(options, conversation);
await foreach (var chunk in base.GetStreamingResponseAsync(messages, conversationOptions, cancellationToken).ConfigureAwait(false))
{
yield return chunk;
}
}
private async Task<string> GetOrCreateConversationAsync(ChatOptions? options, CancellationToken cancellationToken)
=> string.IsNullOrWhiteSpace(options?.ConversationId)
? (await this._agentsClient.GetConversationClient().CreateConversationAsync(cancellationToken: cancellationToken).ConfigureAwait(false)).Value.Id
: options.ConversationId;
private ChatOptions GetConversationEnabledChatOptions(ChatOptions? chatOptions, string conversationId)
{
// Start with a clone of the base chat options defined for the agent, if any.
ChatOptions conversationChatOptions = this._chatOptions?.Clone() ?? new();
// Ignore per-request all options that can't be overriden.
conversationChatOptions.Instructions = null;
conversationChatOptions.Tools = null;
// Preserve the original RawRepresentationFactory
var originalFactory = chatOptions?.RawRepresentationFactory;
conversationChatOptions.RawRepresentationFactory = (client) =>
{
if (originalFactory?.Invoke(this) is not ResponseCreationOptions responseCreationOptions)
{
responseCreationOptions = new ResponseCreationOptions();
}
SetAgentReference(responseCreationOptions, this._agentVersion);
SetConversationReference(responseCreationOptions, conversationId);
return responseCreationOptions;
};
return conversationChatOptions;
}
// Since the SetAdditionalProperty/SetAgentReference/SetConversationReference extensions in Azure.AI.Agents does not yet support the recent updates in OpenAI 2.6.0
// The methods below are copied and adapted to the new OpenAI SDK 2.6.0 structure where the Patch property is now exposed directly on ResponseCreationOptions and
// may be removed once the Azure.AI.Agents package is updated to support OpenAI SDK 2.6+.
#pragma warning disable SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
private static void SetAdditionalProperty(ResponseCreationOptions responseCreationOptions, string key, BinaryData value)
{
responseCreationOptions.Patch.Set([.. "$."u8, .. Encoding.UTF8.GetBytes(key)], value);
}
private static void SetAgentReference(ResponseCreationOptions responseCreationOptions, AgentVersion agentVersion)
{
var agentReference = new AgentReference(agentVersion.Name) { Version = agentVersion.Version };
SetAdditionalProperty(responseCreationOptions, "agent", ModelReaderWriter.Write(agentReference, new ModelReaderWriterOptions("W"), AzureAIAgentsContext.Default));
responseCreationOptions.Patch.Remove([.. "$."u8, .. Encoding.UTF8.GetBytes("model")]);
}
private static void SetConversationReference(ResponseCreationOptions responseCreationOptions, string conversationId)
{
SetAdditionalProperty(responseCreationOptions, "conversation", BinaryData.FromString($"\"{conversationId}\""));
}
#pragma warning restore SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
}
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(ProjectsTargetFrameworks)</TargetFrameworks>
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugTargetFrameworks)</TargetFrameworks>
<VersionSuffix>preview</VersionSuffix>
<ImplicitUsings>enable</ImplicitUsings>
<InjectSharedThrow>true</InjectSharedThrow>
</PropertyGroup>
<Import Project="$(RepoRoot)/dotnet/nuget/nuget-package.props" />
<ItemGroup>
<PackageReference Include="Azure.AI.Agents" />
<PackageReference Include="Microsoft.Extensions.AI" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
<PackageReference Include="OpenAI" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>
<PropertyGroup>
<!-- NuGet Package Settings -->
<Title>Microsoft Agent Framework Azure AI Agents</Title>
<Description>Provides Microsoft Agent Framework support for Azure AI Agents.</Description>
</PropertyGroup>
</Project>
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(ProjectsTargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
</ItemGroup>
</Project>
@@ -68,7 +68,7 @@ public class OpenAIResponseFixture(bool store) : IChatClientAgentFixture
string name = "HelpfulAssistant",
string instructions = "You are a helpful assistant.",
IList<AITool>? aiTools = null) =>
new ChatClientAgent(
new(
this._openAIResponseClient.AsIChatClient(),
options: new()
{