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,97 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
namespace WorkflowAsAnAgentSample;
/// <summary>
/// This sample introduces the concept of workflows as agents, where a workflow can be
/// treated as an <see cref="AIAgent"/>. This allows you to interact with a workflow
/// as if it were a single agent.
///
/// In this example, we create a workflow that uses two language agents to process
/// input concurrently, one that responds in French and another that responds in English.
///
/// You will interact with the workflow in an interactive loop, sending messages and receiving
/// streaming responses from the workflow as if it were an agent who responds in both languages.
///
/// This sample also demonstrates <see cref="IResettableExecutor"/>, which is required
/// for stateful executors that are shared across multiple workflow runs. Each iteration
/// of the interactive loop triggers a new workflow run against the same workflow instance.
/// Between runs, the framework automatically calls <see cref="IResettableExecutor.ResetAsync"/>
/// on shared executors so that accumulated state (e.g., collected messages) is cleared
/// before the next run begins. See <c>WorkflowFactory.ConcurrentAggregationExecutor</c>
/// for the implementation.
/// </summary>
/// <remarks>
/// Pre-requisites:
/// - Foundational samples should be completed first.
/// - This sample uses concurrent processing.
/// - An Azure OpenAI endpoint and deployment name.
/// </remarks>
public static class Program
{
private static async Task Main()
{
// Set up the Azure AI Foundry client
var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
// Create the workflow and turn it into an agent
var workflow = WorkflowFactory.BuildWorkflow(aiProjectClient, deploymentName);
var agent = workflow.AsAIAgent("workflow-agent", "Workflow Agent");
var session = await agent.CreateSessionAsync();
// Start an interactive loop to interact with the workflow as if it were an agent.
// Each iteration runs the workflow again on the same workflow instance. Between runs,
// the framework calls IResettableExecutor.ResetAsync() on shared stateful executors
// (like ConcurrentAggregationExecutor) to clear accumulated state from the previous run.
while (true)
{
Console.WriteLine();
Console.Write("User (or 'exit' to quit): ");
string? input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input) || input.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
break;
}
await ProcessInputAsync(agent, session, input);
}
// Helper method to process user input and display streaming responses. To display
// multiple interleaved responses correctly, we buffer updates by message ID and
// re-render all messages on each update.
static async Task ProcessInputAsync(AIAgent agent, AgentSession? session, string input)
{
Dictionary<string, List<AgentResponseUpdate>> buffer = [];
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(input, session))
{
if (update.MessageId is null || string.IsNullOrEmpty(update.Text))
{
// skip updates that don't have a message ID or text
continue;
}
if (!buffer.TryGetValue(update.MessageId, out List<AgentResponseUpdate>? value))
{
value = [];
buffer[update.MessageId] = value;
}
value.Add(update);
foreach (var (messageId, segments) in buffer)
{
string combinedText = string.Concat(segments);
Console.WriteLine($"{segments[0].AuthorName}: {combinedText}");
Console.WriteLine();
}
}
}
}
}
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,100 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
namespace WorkflowAsAnAgentSample;
internal static class WorkflowFactory
{
/// <summary>
/// Creates a workflow that uses two language agents to process input concurrently.
///
/// In this workflow, the <c>Start</c> <see cref="ChatForwardingExecutor"/> and the
/// <see cref="ConcurrentAggregationExecutor"/> are provided as shared instances, meaning
/// the same executor objects are reused across multiple workflow runs. The language agents
/// (French and English) are created via a factory and instantiated per workflow run.
/// Stateful shared executors must implement <see cref="IResettableExecutor"/> so the
/// framework can clear their state between runs. Framework-provided executors like
/// <see cref="ChatForwardingExecutor"/> already implement this interface.
/// </summary>
/// <param name="client">The AI project client to use for the agents</param>
/// <param name="model">The model deployment name</param>
/// <returns>A workflow that processes input using two language agents</returns>
internal static Workflow BuildWorkflow(AIProjectClient client, string model)
{
// Create executors
var startExecutor = new ChatForwardingExecutor("Start");
var aggregationExecutor = new ConcurrentAggregationExecutor();
AIAgent frenchAgent = GetLanguageAgent("French", client, model);
AIAgent englishAgent = GetLanguageAgent("English", client, model);
// Build the workflow by adding executors and connecting them
return new WorkflowBuilder(startExecutor)
.AddFanOutEdge(startExecutor, [frenchAgent, englishAgent])
.AddFanInBarrierEdge([frenchAgent, englishAgent], aggregationExecutor)
.WithOutputFrom(aggregationExecutor)
.Build();
}
/// <summary>
/// Creates a language agent for the specified target language.
/// </summary>
/// <param name="targetLanguage">The target language for translation</param>
/// <param name="client">The AI project client to use for the agent</param>
/// <param name="model">The model deployment name</param>
/// <returns>A ChatClientAgent configured for the specified language</returns>
private static ChatClientAgent GetLanguageAgent(string targetLanguage, AIProjectClient client, string model) =>
client.AsAIAgent(model: model, instructions: $"You're a helpful assistant who always responds in {targetLanguage}.", name: $"{targetLanguage}Agent");
/// <summary>
/// Executor that aggregates the results from the concurrent agents.
///
/// This executor is stateful — it accumulates messages in <see cref="_messages"/>
/// as they arrive from each agent. Because it is provided as a shared instance
/// (not via a factory), the same object is reused across workflow runs. Implementing
/// <see cref="IResettableExecutor"/> allows the framework to call <see cref="ResetAsync"/>
/// between runs, clearing accumulated state so each run starts fresh.
///
/// Without <see cref="IResettableExecutor"/>, attempting to reuse a workflow containing
/// shared executor instances that do not implement this interface would throw an
/// <see cref="InvalidOperationException"/>.
/// </summary>
[YieldsOutput(typeof(string))]
private sealed class ConcurrentAggregationExecutor() :
Executor<List<ChatMessage>>("ConcurrentAggregationExecutor"), IResettableExecutor
{
private readonly List<ChatMessage> _messages = [];
/// <summary>
/// Handles incoming messages from the agents and aggregates their responses.
/// </summary>
/// <param name="message">The messages from the agent</param>
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
/// The default is <see cref="CancellationToken.None"/>.</param>
public override async ValueTask HandleAsync(List<ChatMessage> message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
this._messages.AddRange(message);
if (this._messages.Count == 2)
{
var formattedMessages = string.Join(Environment.NewLine, this._messages.Select(m => $"{m.Text}"));
await context.YieldOutputAsync(formattedMessages, cancellationToken);
}
}
/// <summary>
/// Resets the executor state between workflow runs by clearing accumulated messages.
/// The framework calls this automatically when a workflow run completes, before the
/// workflow can be used for another run.
/// </summary>
public ValueTask ResetAsync()
{
this._messages.Clear();
return default;
}
}
}