chore: import upstream snapshot with attribution
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:25 +08:00
commit db620d33df
5151 changed files with 925932 additions and 0 deletions
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>AgentOrchestration_Conditionals</AssemblyName>
<RootNamespace>AgentOrchestration_Conditionals</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.DurableTask.Client.AzureManaged" />
<PackageReference Include="Microsoft.DurableTask.Worker.AzureManaged" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
<!-- Local projects that should be switched to package references when using the sample outside of this MAF repo -->
<!--
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.DurableTask" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
</ItemGroup>
-->
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.DurableTask\Microsoft.Agents.AI.DurableTask.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
namespace AgentOrchestration_Conditionals;
/// <summary>
/// Represents an email input for spam detection and response generation.
/// </summary>
public sealed class Email
{
[JsonPropertyName("email_id")]
public string EmailId { get; set; } = string.Empty;
[JsonPropertyName("email_content")]
public string EmailContent { get; set; } = string.Empty;
}
/// <summary>
/// Represents the result of spam detection analysis.
/// </summary>
public sealed class DetectionResult
{
[JsonPropertyName("is_spam")]
public bool IsSpam { get; set; }
[JsonPropertyName("reason")]
public string Reason { get; set; } = string.Empty;
}
/// <summary>
/// Represents a generated email response.
/// </summary>
public sealed class EmailResponse
{
[JsonPropertyName("response")]
public string Response { get; set; } = string.Empty;
}
@@ -0,0 +1,231 @@
// Copyright (c) Microsoft. All rights reserved.
using AgentOrchestration_Conditionals;
using Azure;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.DurableTask;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Client.AzureManaged;
using Microsoft.DurableTask.Worker;
using Microsoft.DurableTask.Worker.AzureManaged;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using OpenAI.Chat;
// Get the Azure OpenAI endpoint and deployment name from environment variables.
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME")
?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set.");
// Get DTS connection string from environment variable
string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SCHEDULER_CONNECTION_STRING")
?? "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None";
// Use Azure Key Credential if provided, otherwise use Azure CLI Credential.
string? azureOpenAiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey)
? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey))
: new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential());
// Spam detection agent
const string SpamDetectionAgentName = "SpamDetectionAgent";
const string SpamDetectionAgentInstructions =
"""
You are an expert email spam detection system. Analyze emails and determine if they are spam.
Return your analysis as JSON with 'is_spam' (boolean) and 'reason' (string) fields.
""";
// Email assistant agent
const string EmailAssistantAgentName = "EmailAssistantAgent";
const string EmailAssistantAgentInstructions =
"""
You are a professional email assistant. Draft professional, courteous, and helpful email responses.
Return your response as JSON with a 'response' field containing the reply.
""";
AIAgent spamDetectionAgent = client.GetChatClient(deploymentName).AsAIAgent(SpamDetectionAgentInstructions, SpamDetectionAgentName);
AIAgent emailAssistantAgent = client.GetChatClient(deploymentName).AsAIAgent(EmailAssistantAgentInstructions, EmailAssistantAgentName);
// Orchestrator function
static async Task<string> RunOrchestratorAsync(TaskOrchestrationContext context, Email email)
{
// Get the spam detection agent
DurableAIAgent spamDetectionAgent = context.GetAgent(SpamDetectionAgentName);
AgentSession spamSession = await spamDetectionAgent.CreateSessionAsync();
// Step 1: Check if the email is spam
AgentResponse<DetectionResult> spamDetectionResponse = await spamDetectionAgent.RunAsync<DetectionResult>(
message:
$"""
Analyze this email for spam content and return a JSON response with 'is_spam' (boolean) and 'reason' (string) fields:
Email ID: {email.EmailId}
Content: {email.EmailContent}
""",
session: spamSession);
DetectionResult result = spamDetectionResponse.Result;
// Step 2: Conditional logic based on spam detection result
if (result.IsSpam)
{
// Handle spam email
return await context.CallActivityAsync<string>(nameof(HandleSpamEmail), result.Reason);
}
// Generate and send response for legitimate email
DurableAIAgent emailAssistantAgent = context.GetAgent(EmailAssistantAgentName);
AgentSession emailSession = await emailAssistantAgent.CreateSessionAsync();
AgentResponse<EmailResponse> emailAssistantResponse = await emailAssistantAgent.RunAsync<EmailResponse>(
message:
$"""
Draft a professional response to this email. Return a JSON response with a 'response' field containing the reply:
Email ID: {email.EmailId}
Content: {email.EmailContent}
""",
session: emailSession);
EmailResponse emailResponse = emailAssistantResponse.Result;
return await context.CallActivityAsync<string>(nameof(SendEmail), emailResponse.Response);
}
// Activity functions
static void HandleSpamEmail(TaskActivityContext context, string reason)
{
Console.WriteLine($"Email marked as spam: {reason}");
}
static void SendEmail(TaskActivityContext context, string message)
{
Console.WriteLine($"Email sent: {message}");
}
// Configure the console app to host the AI agents.
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureLogging(loggingBuilder => loggingBuilder.SetMinimumLevel(LogLevel.Warning))
.ConfigureServices(services =>
{
services.ConfigureDurableAgents(
options =>
{
options
.AddAIAgent(spamDetectionAgent)
.AddAIAgent(emailAssistantAgent);
},
workerBuilder: builder =>
{
builder.UseDurableTaskScheduler(dtsConnectionString);
builder.AddTasks(registry =>
{
registry.AddOrchestratorFunc<Email>(nameof(RunOrchestratorAsync), RunOrchestratorAsync);
registry.AddActivityFunc<string>(nameof(HandleSpamEmail), HandleSpamEmail);
registry.AddActivityFunc<string>(nameof(SendEmail), SendEmail);
});
},
clientBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString));
})
.Build();
await host.StartAsync();
DurableTaskClient durableTaskClient = host.Services.GetRequiredService<DurableTaskClient>();
// Console colors for better UX
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("=== Multi-Agent Conditional Orchestration Sample ===");
Console.ResetColor();
Console.WriteLine("Enter email content:");
Console.WriteLine();
// Read email content from stdin
string? emailContent = Console.ReadLine();
if (string.IsNullOrWhiteSpace(emailContent))
{
Console.ForegroundColor = ConsoleColor.Red;
Console.Error.WriteLine("Error: Email content is required.");
Console.ResetColor();
Environment.Exit(1);
return;
}
// Generate email ID automatically
Email email = new()
{
EmailId = $"email-{Guid.NewGuid():N}",
EmailContent = emailContent
};
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine("Starting orchestration...");
Console.ResetColor();
try
{
// Start the orchestration
string instanceId = await durableTaskClient.ScheduleNewOrchestrationInstanceAsync(
orchestratorName: nameof(RunOrchestratorAsync),
input: email);
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine($"Orchestration started with instance ID: {instanceId}");
Console.WriteLine("Waiting for completion...");
Console.ResetColor();
// Wait for orchestration to complete
OrchestrationMetadata status = await durableTaskClient.WaitForInstanceCompletionAsync(
instanceId,
getInputsAndOutputs: true,
CancellationToken.None);
Console.WriteLine();
if (status.RuntimeStatus == OrchestrationRuntimeStatus.Completed)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("✓ Orchestration completed successfully!");
Console.ResetColor();
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write("Result: ");
Console.ResetColor();
Console.WriteLine(status.ReadOutputAs<string>());
}
else if (status.RuntimeStatus == OrchestrationRuntimeStatus.Failed)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("✗ Orchestration failed!");
Console.ResetColor();
if (status.FailureDetails != null)
{
Console.WriteLine($"Error: {status.FailureDetails.ErrorMessage}");
}
Environment.Exit(1);
}
else
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"Orchestration status: {status.RuntimeStatus}");
Console.ResetColor();
}
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.Error.WriteLine($"Error: {ex.Message}");
Console.ResetColor();
Environment.Exit(1);
}
finally
{
await host.StopAsync();
}
@@ -0,0 +1,95 @@
# Multi-Agent Conditional Orchestration Sample
This sample demonstrates how to use the durable agents extension to create a console app that orchestrates multiple AI agents with conditional logic based on the results of previous agent interactions.
## Key Concepts Demonstrated
- Multi-agent orchestration with conditional branching
- Using agent responses to determine workflow paths
- Activity functions for non-agent operations
- Waiting for orchestration completion using `WaitForInstanceCompletionAsync`
## Environment Setup
See the [README.md](../README.md) file in the parent directory for more information on how to configure the environment, including how to install and run common sample dependencies.
## Running the Sample
With the environment setup, you can run the sample:
```bash
cd dotnet/samples/04-hosting/DurableAgents/ConsoleApps/04_AgentOrchestration_Conditionals
dotnet run --framework net10.0
```
The app will prompt you for email content. You can test both legitimate emails and spam emails:
### Testing with a Legitimate Email
```text
=== Multi-Agent Conditional Orchestration Sample ===
Enter email content:
Hi John, I hope you're doing well. I wanted to follow up on our meeting yesterday about the quarterly report. Could you please send me the updated figures by Friday? Thanks!
```
The orchestration will analyze the email and display the result:
```text
Orchestration started with instance ID: 86313f1d45fb42eeb50b1852626bf3ff
Waiting for completion...
✓ Orchestration completed successfully!
Result: Email sent: Thank you for your email. I'll prepare the updated figures...
```
### Testing with a Spam Email
```text
=== Multi-Agent Conditional Orchestration Sample ===
Enter email content:
URGENT! You've won $1,000,000! Click here now to claim your prize! Limited time offer! Don't miss out!
```
The orchestration will detect it as spam and display:
```text
Orchestration started with instance ID: 86313f1d45fb42eeb50b1852626bf3ff
Waiting for completion...
✓ Orchestration completed successfully!
Result: Email marked as spam: Contains suspicious claims about winning money and urgent action requests...
```
## Scriptable Usage
You can also pipe email content to the app:
```bash
# Test with a legitimate email
echo "Hi John, I hope you're doing well..." | dotnet run
# Test with a spam email
echo "URGENT! You've won $1,000,000! Click here now!" | dotnet run
```
The orchestration will proceed as follows:
1. The SpamDetectionAgent analyzes the email to determine if it's spam
2. Based on the result:
- If spam: The orchestration calls the `HandleSpamEmail` activity function
- If not spam: The EmailAssistantAgent drafts a response, then the `SendEmail` activity function is called
## Viewing Orchestration State
You can view the state of the orchestration in the Durable Task Scheduler dashboard:
1. Open your browser and navigate to `http://localhost:8082`
2. In the dashboard, you can see:
- **Orchestrations**: View the orchestration instance, including its runtime status, input, output, and execution history
- **Agents**: View the state of both the SpamDetectionAgent and EmailAssistantAgent
The orchestration instance ID is displayed in the console output. You can use this ID to find the specific orchestration in the dashboard and inspect the conditional branching logic, including which path was taken based on the spam detection result.