merge with latest main

This commit is contained in:
SergeyMenshykh
2026-02-09 13:26:06 +00:00
Unverified
667 changed files with 27645 additions and 19375 deletions
@@ -28,11 +28,21 @@ namespace SampleApp
{
public override string? Name => "UpperCaseParrotAgent";
public override ValueTask<AgentSession> CreateSessionAsync(CancellationToken cancellationToken = default)
protected override ValueTask<AgentSession> CreateSessionCoreAsync(CancellationToken cancellationToken = default)
=> new(new CustomAgentSession());
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
=> new(new CustomAgentSession(serializedSession, jsonSerializerOptions));
protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
{
if (session is not CustomAgentSession typedSession)
{
throw new ArgumentException($"The provided session is not of type {nameof(CustomAgentSession)}.", nameof(session));
}
return typedSession.Serialize(jsonSerializerOptions);
}
protected override ValueTask<AgentSession> DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
=> new(new CustomAgentSession(serializedState, jsonSerializerOptions));
protected override async Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
@@ -45,14 +55,14 @@ namespace SampleApp
}
// Get existing messages from the store
var invokingContext = new ChatHistoryProvider.InvokingContext(messages);
var invokingContext = new ChatHistoryProvider.InvokingContext(this, session, messages);
var storeMessages = await typedSession.ChatHistoryProvider.InvokingAsync(invokingContext, cancellationToken);
// Clone the input messages and turn them into response messages with upper case text.
List<ChatMessage> responseMessages = CloneAndToUpperCase(messages, this.Name).ToList();
// Notify the session of the input and output messages.
var invokedContext = new ChatHistoryProvider.InvokedContext(messages, storeMessages)
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, messages, storeMessages)
{
ResponseMessages = responseMessages
};
@@ -77,14 +87,14 @@ namespace SampleApp
}
// Get existing messages from the store
var invokingContext = new ChatHistoryProvider.InvokingContext(messages);
var invokingContext = new ChatHistoryProvider.InvokingContext(this, session, messages);
var storeMessages = await typedSession.ChatHistoryProvider.InvokingAsync(invokingContext, cancellationToken);
// Clone the input messages and turn them into response messages with upper case text.
List<ChatMessage> responseMessages = CloneAndToUpperCase(messages, this.Name).ToList();
// Notify the session of the input and output messages.
var invokedContext = new ChatHistoryProvider.InvokedContext(messages, storeMessages)
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, messages, storeMessages)
{
ResponseMessages = responseMessages
};
@@ -136,6 +146,9 @@ namespace SampleApp
internal CustomAgentSession(JsonElement serializedSessionState, JsonSerializerOptions? jsonSerializerOptions = null)
: base(serializedSessionState, jsonSerializerOptions) { }
internal new JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
=> base.Serialize(jsonSerializerOptions);
}
}
}
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Anthropic\Microsoft.Agents.AI.Anthropic.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,127 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates how to use Anthropic-managed Skills with an AI agent.
// Skills are pre-built capabilities provided by Anthropic that can be used with the Claude API.
// This sample shows how to:
// 1. List available Anthropic-managed skills
// 2. Use the pptx skill to create PowerPoint presentations
// 3. Download and save generated files
using Anthropic;
using Anthropic.Core;
using Anthropic.Models.Beta;
using Anthropic.Models.Beta.Files;
using Anthropic.Models.Beta.Messages;
using Anthropic.Models.Beta.Skills;
using Anthropic.Services;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
string apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY") ?? throw new InvalidOperationException("ANTHROPIC_API_KEY is not set.");
// Skills require Claude 4.5 models (Sonnet 4.5, Haiku 4.5, or Opus 4.5)
string model = Environment.GetEnvironmentVariable("ANTHROPIC_MODEL") ?? "claude-sonnet-4-5-20250929";
// Create the Anthropic client
AnthropicClient anthropicClient = new() { ApiKey = apiKey };
// List available Anthropic-managed skills (optional - API may not be available in all regions)
Console.WriteLine("Available Anthropic-managed skills:");
try
{
SkillListPage skills = await anthropicClient.Beta.Skills.List(
new SkillListParams { Source = "anthropic", Betas = [AnthropicBeta.Skills2025_10_02] });
foreach (var skill in skills.Items)
{
Console.WriteLine($" {skill.Source}: {skill.ID} (version: {skill.LatestVersion})");
}
}
catch (Exception ex)
{
Console.WriteLine($" (Skills listing not available: {ex.Message})");
}
Console.WriteLine();
// Define the pptx skill - the SDK handles all beta flags and container configuration automatically
// when using AsAITool(), so no manual RawRepresentationFactory configuration is needed.
BetaSkillParams pptxSkill = new()
{
Type = BetaSkillParamsType.Anthropic,
SkillID = "pptx",
Version = "latest"
};
// Create an agent with the pptx skill enabled.
// Skills require extended thinking and higher max tokens for complex file generation.
// The SDK's AsAITool() handles beta flags and container config automatically.
ChatClientAgent agent = anthropicClient.Beta.AsAIAgent(
model: model,
instructions: "You are a helpful agent for creating PowerPoint presentations.",
tools: [pptxSkill.AsAITool()],
clientFactory: (chatClient) => chatClient
.AsBuilder()
.ConfigureOptions(options =>
{
options.RawRepresentationFactory = (_) => new MessageCreateParams()
{
Model = model,
MaxTokens = 20000,
Messages = [],
Thinking = new BetaThinkingConfigParam(
new BetaThinkingConfigEnabled(budgetTokens: 10000))
};
})
.Build());
Console.WriteLine("Creating a presentation about renewable energy...\n");
// Run the agent with a request to create a presentation
AgentResponse response = await agent.RunAsync("Create a simple 3-slide presentation about renewable energy sources. Include a title slide, a slide about solar energy, and a slide about wind energy.");
Console.WriteLine("#### Agent Response ####");
Console.WriteLine(response.Text);
// Display any reasoning/thinking content
List<TextReasoningContent> reasoningContents = response.Messages.SelectMany(m => m.Contents.OfType<TextReasoningContent>()).ToList();
if (reasoningContents.Count > 0)
{
Console.WriteLine("\n#### Agent Reasoning ####");
Console.WriteLine($"\e[92m{string.Join("\n", reasoningContents.Select(c => c.Text))}\e[0m");
}
// Collect generated files from CodeInterpreterToolResultContent outputs
List<HostedFileContent> hostedFiles = response.Messages
.SelectMany(m => m.Contents.OfType<CodeInterpreterToolResultContent>())
.Where(c => c.Outputs is not null)
.SelectMany(c => c.Outputs!.OfType<HostedFileContent>())
.ToList();
if (hostedFiles.Count > 0)
{
Console.WriteLine("\n#### Generated Files ####");
foreach (HostedFileContent file in hostedFiles)
{
Console.WriteLine($" FileId: {file.FileId}");
// Download the file using the Anthropic Files API
using HttpResponse fileResponse = await anthropicClient.Beta.Files.Download(
file.FileId,
new FileDownloadParams { Betas = ["files-api-2025-04-14"] });
// Save the file to disk
string fileName = $"presentation_{file.FileId.Substring(0, 8)}.pptx";
using FileStream fileStream = File.Create(fileName);
Stream contentStream = await fileResponse.ReadAsStream();
await contentStream.CopyToAsync(fileStream);
Console.WriteLine($" Saved to: {fileName}");
}
}
Console.WriteLine("\nToken usage:");
Console.WriteLine($"Input: {response.Usage?.InputTokenCount}, Output: {response.Usage?.OutputTokenCount}");
if (response.Usage?.AdditionalCounts is not null)
{
Console.WriteLine($"Additional: {string.Join(", ", response.Usage.AdditionalCounts)}");
}
@@ -0,0 +1,119 @@
# Using Anthropic Skills with agents
This sample demonstrates how to use Anthropic-managed Skills with AI agents. Skills are pre-built capabilities provided by Anthropic that can be used with the Claude API.
## What this sample demonstrates
- Listing available Anthropic-managed skills
- Creating an AI agent with Anthropic Claude Skills support using the simplified `AsAITool()` approach
- Using the pptx skill to create PowerPoint presentations
- Downloading and saving generated files to disk
- Handling agent responses with generated content
## Prerequisites
Before you begin, ensure you have the following prerequisites:
- .NET 10.0 SDK or later
- Anthropic API key configured
- Access to Anthropic Claude models with Skills support
**Note**: This sample uses Anthropic Claude models with Skills. Skills are a beta feature. For more information, see [Anthropic documentation](https://docs.anthropic.com/).
Set the following environment variables:
```powershell
$env:ANTHROPIC_API_KEY="your-anthropic-api-key" # Replace with your Anthropic API key
$env:ANTHROPIC_MODEL="your-anthropic-model" # Replace with your Anthropic model (e.g., claude-sonnet-4-5-20250929)
```
## Run the sample
Navigate to the AgentWithAnthropic sample directory and run:
```powershell
cd dotnet\samples\GettingStarted\AgentWithAnthropic
dotnet run --project .\Agent_Anthropic_Step04_UsingSkills
```
## Available Anthropic Skills
Anthropic provides several managed skills that can be used with the Claude API:
- `pptx` - Create PowerPoint presentations
- `xlsx` - Create Excel spreadsheets
- `docx` - Create Word documents
- `pdf` - Create and analyze PDF documents
You can list available skills using the Anthropic SDK:
```csharp
SkillListPage skills = await anthropicClient.Beta.Skills.List(
new SkillListParams { Source = "anthropic", Betas = [AnthropicBeta.Skills2025_10_02] });
foreach (var skill in skills.Items)
{
Console.WriteLine($"{skill.Source}: {skill.ID} (version: {skill.LatestVersion})");
}
```
## Expected behavior
The sample will:
1. List all available Anthropic-managed skills
2. Create an agent with the pptx skill enabled
3. Run the agent with a request to create a presentation
4. Display the agent's response text
5. Download any generated files and save them to disk
6. Display token usage statistics
## Code highlights
### Simplified skill configuration
The Anthropic SDK handles all beta flags and container configuration automatically when using `AsAITool()`:
```csharp
// Define the pptx skill
BetaSkillParams pptxSkill = new()
{
Type = BetaSkillParamsType.Anthropic,
SkillID = "pptx",
Version = "latest"
};
// Create an agent - the SDK handles beta flags automatically!
ChatClientAgent agent = anthropicClient.Beta.AsAIAgent(
model: model,
instructions: "You are a helpful agent for creating PowerPoint presentations.",
tools: [pptxSkill.AsAITool()]);
```
**Note**: No manual `RawRepresentationFactory`, `Betas`, or `Container` configuration is needed. The SDK automatically adds the required beta headers (`skills-2025-10-02`, `code-execution-2025-08-25`) and configures the container with the skill.
### Handling generated files
Generated files are returned as `HostedFileContent` within `CodeInterpreterToolResultContent`:
```csharp
// Collect generated files from response
List<HostedFileContent> hostedFiles = response.Messages
.SelectMany(m => m.Contents.OfType<CodeInterpreterToolResultContent>())
.Where(c => c.Outputs is not null)
.SelectMany(c => c.Outputs!.OfType<HostedFileContent>())
.ToList();
// Download and save each file
foreach (HostedFileContent file in hostedFiles)
{
using HttpResponse fileResponse = await anthropicClient.Beta.Files.Download(
file.FileId,
new FileDownloadParams { Betas = ["files-api-2025-04-14"] });
string fileName = $"presentation_{file.FileId.Substring(0, 8)}.pptx";
await using FileStream fileStream = File.Create(fileName);
Stream contentStream = await fileResponse.ReadAsStream();
await contentStream.CopyToAsync(fileStream);
}
```
@@ -29,6 +29,7 @@ To use Anthropic with Azure Foundry, you can check the sample [AgentProviders/Ag
|[Running a simple agent](./Agent_Anthropic_Step01_Running/)|This sample demonstrates how to create and run a basic agent with Anthropic Claude|
|[Using reasoning with an agent](./Agent_Anthropic_Step02_Reasoning/)|This sample demonstrates how to use extended thinking/reasoning capabilities with Anthropic Claude agents|
|[Using function tools with an agent](./Agent_Anthropic_Step03_UsingFunctionTools/)|This sample demonstrates how to use function tools with an Anthropic Claude agent|
|[Using Skills with an agent](./Agent_Anthropic_Step04_UsingSkills/)|This sample demonstrates how to use Anthropic-managed Skills (e.g., pptx) with an Anthropic Claude agent|
## Running the samples from the console
@@ -55,7 +55,7 @@ await Task.Delay(TimeSpan.FromSeconds(2));
Console.WriteLine(await agent.RunAsync("What do you already know about my upcoming trip?", session));
Console.WriteLine("\n>> Serialize and deserialize the session to demonstrate persisted state\n");
JsonElement serializedSession = session.Serialize();
JsonElement serializedSession = agent.SerializeSession(session);
AgentSession restoredSession = await agent.DeserializeSessionAsync(serializedSession);
Console.WriteLine(await agent.RunAsync("Can you recap the personal details you remember?", restoredSession));
@@ -47,7 +47,7 @@ Console.WriteLine(await agent.RunAsync("My name is Ruaidhrí", session));
Console.WriteLine(await agent.RunAsync("I am 20 years old", session));
// We can serialize the session. The serialized state will include the state of the memory component.
var sesionElement = session.Serialize();
JsonElement sesionElement = agent.SerializeSession(session);
Console.WriteLine("\n>> Use deserialized session with previously created memories\n");
@@ -29,36 +29,34 @@ AIAgent agent = new AzureOpenAIClient(
.GetChatClient(deploymentName)
.AsAIAgent(instructions: "You are a helpful assistant", tools: [new ApprovalRequiredAIFunction(AIFunctionFactory.Create(GetWeather))]);
// Call the agent and check if there are any user input requests to handle.
// Call the agent and check if there are any function approval requests to handle.
// For simplicity, we are assuming here that only function approvals are pending.
AgentSession session = await agent.CreateSessionAsync();
var response = await agent.RunAsync("What is the weather like in Amsterdam?", session);
var userInputRequests = response.UserInputRequests.ToList();
AgentResponse response = await agent.RunAsync("What is the weather like in Amsterdam?", session);
List<FunctionApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<FunctionApprovalRequestContent>().ToList();
// For streaming use:
// var updates = await agent.RunStreamingAsync("What is the weather like in Amsterdam?", session).ToListAsync();
// userInputRequests = updates.SelectMany(x => x.UserInputRequests).ToList();
// approvalRequests = updates.SelectMany(x => x.Contents).OfType<FunctionApprovalRequestContent>().ToList();
while (userInputRequests.Count > 0)
while (approvalRequests.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 userInputResponses = userInputRequests
.OfType<FunctionApprovalRequestContent>()
.Select(functionApprovalRequest =>
List<ChatMessage> userInputResponses = approvalRequests
.ConvertAll(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();
});
// Pass the user input responses back to the agent for further processing.
response = await agent.RunAsync(userInputResponses, session);
userInputRequests = response.UserInputRequests.ToList();
approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<FunctionApprovalRequestContent>().ToList();
// For streaming use:
// updates = await agent.RunStreamingAsync(userInputResponses, session).ToListAsync();
// userInputRequests = updates.SelectMany(x => x.UserInputRequests).ToList();
// approvalRequests = updates.SelectMany(x => x.Contents).OfType<FunctionApprovalRequestContent>().ToList();
}
Console.WriteLine($"\nAgent: {response}");
@@ -25,7 +25,7 @@ AgentSession session = await agent.CreateSessionAsync();
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session));
// Serialize the session state to a JsonElement, so it can be stored for later use.
JsonElement serializedSession = session.Serialize();
JsonElement serializedSession = agent.SerializeSession(session);
// Save the serialized session to a temporary file (for demonstration purposes).
string tempFilePath = Path.GetTempFileName();
@@ -2,7 +2,9 @@
#pragma warning disable CA1869 // Cache and reuse 'JsonSerializerOptions' instances
// This sample shows how to create and use a simple AI agent with a conversation that can be persisted to disk.
// This sample shows how to create and use a simple AI agent with custom ChatHistoryProvider that stores chat history in a custom storage location.
// The state of the custom ChatHistoryProvider (SessionDbKey) is stored with the agent session, so that when the session is resumed later,
// the chat history can be retrieved from the custom storage location.
using System.Text.Json;
using Azure.AI.OpenAI;
@@ -47,7 +49,7 @@ Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session
// Serialize the session state, so it can be stored for later use.
// Since the chat history is stored in the vector store, the serialized session
// only contains the guid that the messages are stored under in the vector store.
JsonElement serializedSession = session.Serialize();
JsonElement serializedSession = agent.SerializeSession(session);
Console.WriteLine("\n--- Serialized session ---\n");
Console.WriteLine(JsonSerializer.Serialize(serializedSession, new JsonSerializerOptions { WriteIndented = true }));
@@ -40,7 +40,7 @@ AgentResponse response = await agent.RunAsync("Write a very long novel about a t
// Poll for background responses until complete.
while (response.ContinuationToken is not null)
{
PersistAgentState(session, response.ContinuationToken);
PersistAgentState(agent, session, response.ContinuationToken);
await Task.Delay(TimeSpan.FromSeconds(10));
@@ -52,9 +52,9 @@ while (response.ContinuationToken is not null)
Console.WriteLine(response.Text);
void PersistAgentState(AgentSession? session, ResponseContinuationToken? continuationToken)
void PersistAgentState(AIAgent agent, AgentSession? session, ResponseContinuationToken? continuationToken)
{
stateStore["session"] = session!.Serialize();
stateStore["session"] = agent.SerializeSession(session!);
stateStore["continuationToken"] = JsonSerializer.SerializeToElement(continuationToken, AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ResponseContinuationToken)));
}
@@ -210,28 +210,25 @@ async Task<AgentResponse> GuardrailMiddleware(IEnumerable<ChatMessage> messages,
// This middleware handles Human in the loop console interaction for any user approval required during function calling.
async Task<AgentResponse> ConsolePromptingApprovalMiddleware(IEnumerable<ChatMessage> messages, AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
{
var response = await innerAgent.RunAsync(messages, session, options, cancellationToken);
AgentResponse response = await innerAgent.RunAsync(messages, session, options, cancellationToken);
var userInputRequests = response.UserInputRequests.ToList();
// For simplicity, we are assuming here that only function approvals are pending.
List<FunctionApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<FunctionApprovalRequestContent>().ToList();
while (userInputRequests.Count > 0)
while (approvalRequests.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 =>
response.Messages = approvalRequests
.ConvertAll(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, session, options, cancellationToken);
userInputRequests = response.UserInputRequests.ToList();
approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<FunctionApprovalRequestContent>().ToList();
}
return response;
@@ -65,7 +65,7 @@ Console.WriteLine(await agent.RunAsync("I need to make a dentist appointment for
Console.WriteLine(await agent.RunAsync("I've taken Sally to soccer practice.", session) + "\n");
// We can serialize the session, and it will contain both the chat history and the data that each AI context provider serialized.
JsonElement serializedSession = session.Serialize();
JsonElement serializedSession = agent.SerializeSession(session);
// Let's print it to console to show the contents.
Console.WriteLine(JsonSerializer.Serialize(serializedSession, options: new JsonSerializerOptions() { WriteIndented = true, IndentSize = 2 }) + "\n");
// The serialized session can be stored long term in a persistent store, but in this case we will just deserialize again and continue the conversation.
@@ -33,7 +33,7 @@ Before you begin, ensure you have the following prerequisites:
|[Using function tools with approvals](./Agent_Step04_UsingFunctionToolsWithApprovals/)|This sample demonstrates how to use function tools where approvals require human in the loop approvals before execution|
|[Structured output with a simple agent](./Agent_Step05_StructuredOutput/)|This sample demonstrates how to use structured output with a simple agent|
|[Persisted conversations with a simple agent](./Agent_Step06_PersistedConversations/)|This sample demonstrates how to persist conversations and reload them later. This is useful for cases where an agent is hosted in a stateless service|
|[3rd party thread storage with a simple agent](./Agent_Step07_3rdPartyThreadStorage/)|This sample demonstrates how to store conversation history in a 3rd party storage solution|
|[3rd party chat history storage with a simple agent](./Agent_Step07_3rdPartyChatHistoryStorage/)|This sample demonstrates how to store chat history in a 3rd party storage solution|
|[Observability with a simple agent](./Agent_Step08_Observability/)|This sample demonstrates how to add telemetry to a simple agent|
|[Dependency injection with a simple agent](./Agent_Step09_DependencyInjection/)|This sample demonstrates how to add and resolve an agent with a dependency injection container|
|[Exposing a simple agent as MCP tool](./Agent_Step10_AsMcpTool/)|This sample demonstrates how to expose an agent as an MCP tool|
@@ -35,27 +35,25 @@ AIAgent agent = await aiProjectClient.CreateAIAgentAsync(name: AssistantName, mo
AgentSession session = await agent.CreateSessionAsync();
AgentResponse response = await agent.RunAsync("What is the weather like in Amsterdam?", session);
// Check if there are any user input requests (approvals needed).
List<UserInputRequestContent> userInputRequests = response.UserInputRequests.ToList();
// Check if there are any approval requests.
// For simplicity, we are assuming here that only function approvals are pending.
List<FunctionApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<FunctionApprovalRequestContent>().ToList();
while (userInputRequests.Count > 0)
while (approvalRequests.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.
List<ChatMessage> userInputMessages = userInputRequests
.OfType<FunctionApprovalRequestContent>()
.Select(functionApprovalRequest =>
List<ChatMessage> userInputMessages = approvalRequests
.ConvertAll(functionApprovalRequest =>
{
Console.WriteLine($"The agent would like to invoke the following function, please reply Y to approve: Name {functionApprovalRequest.FunctionCall.Name}");
bool 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, session);
userInputRequests = response.UserInputRequests.ToList();
approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<FunctionApprovalRequestContent>().ToList();
}
Console.WriteLine($"\nAgent: {response}");
@@ -25,7 +25,7 @@ AgentSession session = await agent.CreateSessionAsync();
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session));
// Serialize the session state to a JsonElement, so it can be stored for later use.
JsonElement serializedSession = session.Serialize();
JsonElement serializedSession = agent.SerializeSession(session);
// Save the serialized session to a temporary file (for demonstration purposes).
string tempFilePath = Path.GetTempFileName();
@@ -193,27 +193,24 @@ async Task<AgentResponse> ConsolePromptingApprovalMiddleware(IEnumerable<ChatMes
{
AgentResponse response = await innerAgent.RunAsync(messages, session, options, cancellationToken);
List<UserInputRequestContent> userInputRequests = response.UserInputRequests.ToList();
// For simplicity, we are assuming here that only function approvals are pending.
List<FunctionApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<FunctionApprovalRequestContent>().ToList();
while (userInputRequests.Count > 0)
while (approvalRequests.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 =>
response.Messages = approvalRequests
.ConvertAll(functionApprovalRequest =>
{
Console.WriteLine($"The agent would like to invoke the following function, please reply Y to approve: Name {functionApprovalRequest.FunctionCall.Name}");
bool approved = Console.ReadLine()?.Equals("Y", StringComparison.OrdinalIgnoreCase) ?? false;
return new ChatMessage(ChatRole.User, [functionApprovalRequest.CreateResponse(approved)]);
})
.ToList();
});
response = await innerAgent.RunAsync(response.Messages, session, options, cancellationToken);
userInputRequests = response.UserInputRequests.ToList();
approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<FunctionApprovalRequestContent>().ToList();
}
return response;
@@ -75,17 +75,16 @@ AIAgent agentWithRequiredApproval = await persistentAgentsClient.CreateAIAgentAs
});
// You can then invoke the agent like any other AIAgent.
var sessionWithRequiredApproval = await agentWithRequiredApproval.CreateSessionAsync();
var response = await agentWithRequiredApproval.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", sessionWithRequiredApproval);
var userInputRequests = response.UserInputRequests.ToList();
// For simplicity, we are assuming here that only mcp tool approvals are pending.
AgentSession sessionWithRequiredApproval = await agentWithRequiredApproval.CreateSessionAsync();
AgentResponse response = await agentWithRequiredApproval.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", sessionWithRequiredApproval);
List<McpServerToolApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<McpServerToolApprovalRequestContent>().ToList();
while (userInputRequests.Count > 0)
while (approvalRequests.Count > 0)
{
// Ask the user to approve each MCP call request.
// For simplicity, we are assuming here that only MCP approval requests are being made.
var userInputResponses = userInputRequests
.OfType<McpServerToolApprovalRequestContent>()
.Select(approvalRequest =>
List<ChatMessage> userInputResponses = approvalRequests
.ConvertAll(approvalRequest =>
{
Console.WriteLine($"""
The agent would like to invoke the following MCP Tool, please reply Y to approve.
@@ -94,13 +93,12 @@ while (userInputRequests.Count > 0)
Arguments: {string.Join(", ", approvalRequest.ToolCall.Arguments?.Select(x => $"{x.Key}: {x.Value}") ?? [])}
""");
return new ChatMessage(ChatRole.User, [approvalRequest.CreateResponse(Console.ReadLine()?.Equals("Y", StringComparison.OrdinalIgnoreCase) ?? false)]);
})
.ToList();
});
// Pass the user input responses back to the agent for further processing.
response = await agentWithRequiredApproval.RunAsync(userInputResponses, sessionWithRequiredApproval);
userInputRequests = response.UserInputRequests.ToList();
approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<McpServerToolApprovalRequestContent>().ToList();
}
Console.WriteLine($"\nAgent: {response}");
@@ -64,17 +64,16 @@ AIAgent agentWithRequiredApproval = new AzureOpenAIClient(
tools: [mcpToolWithApproval]);
// You can then invoke the agent like any other AIAgent.
var sessionWithRequiredApproval = await agentWithRequiredApproval.CreateSessionAsync();
var response = await agentWithRequiredApproval.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", sessionWithRequiredApproval);
var userInputRequests = response.UserInputRequests.ToList();
// For simplicity, we are assuming here that only mcp tool approvals are pending.
AgentSession sessionWithRequiredApproval = await agentWithRequiredApproval.CreateSessionAsync();
AgentResponse response = await agentWithRequiredApproval.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", sessionWithRequiredApproval);
List<McpServerToolApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<McpServerToolApprovalRequestContent>().ToList();
while (userInputRequests.Count > 0)
while (approvalRequests.Count > 0)
{
// Ask the user to approve each MCP call request.
// For simplicity, we are assuming here that only MCP approval requests are being made.
var userInputResponses = userInputRequests
.OfType<McpServerToolApprovalRequestContent>()
.Select(approvalRequest =>
List<ChatMessage> userInputResponses = approvalRequests
.ConvertAll(approvalRequest =>
{
Console.WriteLine($"""
The agent would like to invoke the following MCP Tool, please reply Y to approve.
@@ -83,13 +82,12 @@ while (userInputRequests.Count > 0)
Arguments: {string.Join(", ", approvalRequest.ToolCall.Arguments?.Select(x => $"{x.Key}: {x.Value}") ?? [])}
""");
return new ChatMessage(ChatRole.User, [approvalRequest.CreateResponse(Console.ReadLine()?.Equals("Y", StringComparison.OrdinalIgnoreCase) ?? false)]);
})
.ToList();
});
// Pass the user input responses back to the agent for further processing.
response = await agentWithRequiredApproval.RunAsync(userInputResponses, sessionWithRequiredApproval);
userInputRequests = response.UserInputRequests.ToList();
approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<McpServerToolApprovalRequestContent>().ToList();
}
Console.WriteLine($"\nAgent: {response}");
@@ -72,8 +72,8 @@ public static class Program
/// <summary>
/// Executor that starts the concurrent processing by sending messages to the agents.
/// </summary>
internal sealed class ConcurrentStartExecutor() :
Executor<string>("ConcurrentStartExecutor")
internal sealed partial class ConcurrentStartExecutor() :
Executor("ConcurrentStartExecutor")
{
/// <summary>
/// Starts the concurrent processing by sending messages to the agents.
@@ -83,7 +83,8 @@ internal sealed class ConcurrentStartExecutor() :
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
/// The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A task representing the asynchronous operation</returns>
public override async ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
[MessageHandler]
public async ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Broadcast the message to all connected agents. Receiving agents will queue
// the message but will not start processing until they receive a turn token.
@@ -0,0 +1,13 @@
<Project>
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
<!-- Include Workflows source generator for samples using [MessageHandler] attribute -->
<ItemGroup>
<ProjectReference Include="$(RepoRoot)/dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Microsoft.Agents.AI.Workflows.Generators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"
GlobalPropertiesToRemove="TargetFramework" />
</ItemGroup>
</Project>