Files
Chris 2b869c2396 .NET Workflows - WIP Declarative action update (#1761)
* WIP

* Fixed build errors (#1638)

Comment and nullable type alignment

* Sync to SDK update

* Checkpoint

* Checkpoint: Tests passing

* Checkpoint: EndWorkflow

* Add trace

* .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

* Checkpoint

* Update Directory.Packages.props

Fix package version rollback:

Azure.AI.Agents.Persistent (beta-6 => beta-7)

* Add project reference

* .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>

* .NET: Change model to be required just for prompt agent definition specific extensions (#1812)

* Remove unneeded model from extensions

* Add noop justification

* Update Package Nameing: V1 -> AzureAI.Persistent / V2 -> AzureAI (#1829)

* Checkpoint for merge

* No build errors

* .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>

* Updated package version (#1897)

* Version update (#1901)

* Checkpoint

* Updated package version (#1906)

* Checkpoint

* Checkpoint

* Checkpoint

* Align with azure ai agent

* Update dotnet/samples/GettingStarted/Workflows/Declarative/StudentTeacher/Program.cs

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

* Update dotnet/samples/GettingStarted/Workflows/Declarative/MCPToolApproval/Program.cs

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

* Update dotnet/samples/GettingStarted/Workflows/Declarative/DeepResearch/Program.cs

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

* Refactored external input

* Update dotnet/samples/GettingStarted/Workflows/Declarative/MCPToolApproval/Program.cs

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

* Agent tools patch

* Demos validated

* Checkpoint

* Hygiene

* Checkpoint - Samples

* Update dotnet/samples/GettingStarted/Workflows/Declarative/StudentTeacher/Program.cs

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

* Update dotnet/samples/GettingStarted/Workflows/Declarative/StudentTeacher/Program.cs

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

* Checkpoint

* Checkpoint - Deep Research

* Update baseline

* Update

* Typo

* Checkpoint

* Typos

* Sample cleanup

* Update dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/AzureAgentProvider.cs

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

* Update dotnet/src/Microsoft.Agents.AI.AzureAI/AgentsClientExtensions.cs

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

* Update dotnet/samples/GettingStarted/Workflows/Declarative/FunctionTools/Program.cs

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

* Update dotnet/samples/GettingStarted/Workflows/Declarative/StudentTeacher/Program.cs

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

* Update dotnet/samples/GettingStarted/Workflows/Declarative/ToolApproval/Program.cs

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

* Update dotnet/samples/GettingStarted/Workflows/Declarative/DeepResearch/Program.cs

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

* Typo

* Typo

* Fix input loop

* Sample - Function Calling / External Input

* Typo

* Finessed

* Checkpoint

* Fix feed

* Checkpoint - so close

* Ding dong!

* "there" ***

* Fixup comments

* Fix sample

* Code analysis

* Header

* Typo (variableName)

* Remove dead code

* Skip test (agent api ratchet)

* Comment

* Update dotnet/samples/GettingStarted/Workflows/Declarative/StudentTeacher/Program.cs

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

* Typo

---------

Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>
2025-11-07 11:37:21 -08:00

201 lines
8.3 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI.Workflows.UnitTests;
/// <summary>
/// Tests for InProcessExecution to verify streaming and non-streaming execution behavior.
/// </summary>
public class InProcessExecutionTests
{
/// <summary>
/// The non-streaming version (RunAsync) should execute the workflow and produce events,
/// similar to the streaming version (StreamAsync + TrySendMessageAsync).
/// </summary>
[Fact]
public async Task RunAsyncShouldExecuteWorkflowAsync()
{
// Arrange: Create a simple agent that responds to messages
var agent = new SimpleTestAgent("test-agent");
var workflow = AgentWorkflowBuilder.BuildSequential(agent);
var inputMessage = new ChatMessage(ChatRole.User, "Hello");
// Act: Execute using non-streaming RunAsync
Run run = await InProcessExecution.RunAsync(workflow, new List<ChatMessage> { inputMessage });
// Assert: The workflow should have executed and produced events
RunStatus status = await run.GetStatusAsync();
status.Should().Be(RunStatus.Idle, "workflow should complete execution");
// The run should have events (at minimum, a WorkflowOutputEvent)
run.OutgoingEvents.Should().NotBeEmpty("workflow should produce events during execution");
// Check that we have an agent execution event
var agentEvents = run.OutgoingEvents.OfType<AgentRunUpdateEvent>().ToList();
agentEvents.Should().NotBeEmpty("agent should have executed and produced update events");
// Check that we have output events
var outputEvents = run.OutgoingEvents.OfType<WorkflowOutputEvent>().ToList();
outputEvents.Should().NotBeEmpty("workflow should produce output events");
}
/// <summary>
/// This test shows that the streaming version works correctly when TurnToken is sent following a message.
/// </summary>
[Fact]
public async Task StreamAsyncWithTurnTokenShouldExecuteWorkflowAsync()
{
// Arrange: Create a simple agent that responds to messages
var agent = new SimpleTestAgent("test-agent");
var workflow = AgentWorkflowBuilder.BuildSequential(agent);
var inputMessage = new ChatMessage(ChatRole.User, "Hello");
// Act: Execute using streaming version with TurnToken
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, new List<ChatMessage> { inputMessage });
// Send TurnToken to actually trigger execution (this is the key step)
bool messageSent = await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
messageSent.Should().BeTrue("TurnToken should be accepted");
// Collect events
List<WorkflowEvent> events = [];
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
events.Add(evt);
}
// Assert: The workflow should have executed and produced events
RunStatus status = await run.GetStatusAsync();
status.Should().Be(RunStatus.Idle, "workflow should complete execution");
events.Should().NotBeEmpty("workflow should produce events during execution");
// Check that we have agent execution events
var agentEvents = events.OfType<AgentRunUpdateEvent>().ToList();
agentEvents.Should().NotBeEmpty("agent should have executed and produced update events");
// Check that we have output events
var outputEvents = events.OfType<WorkflowOutputEvent>().ToList();
outputEvents.Should().NotBeEmpty("workflow should produce output events");
}
/// <summary>
/// This test compares the behavior of RunAsync vs StreamAsync to highlight the difference.
/// Both should produce similar results, but as of issue #1315, RunAsync fails to execute.
/// </summary>
[Fact]
public async Task RunAsyncAndStreamAsyncShouldProduceSimilarResultsAsync()
{
// Arrange: Create the same workflow for both tests
var agent1 = new SimpleTestAgent("test-agent-1");
var workflow1 = AgentWorkflowBuilder.BuildSequential(agent1);
var agent2 = new SimpleTestAgent("test-agent-2");
var workflow2 = AgentWorkflowBuilder.BuildSequential(agent2);
var inputMessage = new ChatMessage(ChatRole.User, "Test message");
// Act 1: Execute using RunAsync (non-streaming)
Run nonStreamingRun = await InProcessExecution.RunAsync(workflow1, new List<ChatMessage> { inputMessage });
var nonStreamingEvents = nonStreamingRun.OutgoingEvents.ToList();
// Act 2: Execute using StreamAsync (streaming) with TurnToken
await using StreamingRun streamingRun = await InProcessExecution.StreamAsync(workflow2, new List<ChatMessage> { inputMessage });
await streamingRun.TrySendMessageAsync(new TurnToken(emitEvents: true));
List<WorkflowEvent> streamingEvents = new();
await foreach (WorkflowEvent evt in streamingRun.WatchStreamAsync())
{
streamingEvents.Add(evt);
}
// Assert: Both should have produced events
// The streaming version works (we know this from the issue report)
streamingEvents.Should().NotBeEmpty("streaming version should produce events");
// The non-streaming version should also produce events (this is the bug being tested)
nonStreamingEvents.Should().NotBeEmpty("non-streaming version should also produce events");
// Both should have similar types of events
var streamingAgentEvents = streamingEvents.OfType<AgentRunUpdateEvent>().Count();
var nonStreamingAgentEvents = nonStreamingEvents.OfType<AgentRunUpdateEvent>().Count();
nonStreamingAgentEvents.Should().Be(streamingAgentEvents,
"both versions should produce the same number of agent events");
}
/// <summary>
/// Simple test agent that echoes back the input message.
/// </summary>
private sealed class SimpleTestAgent : AIAgent
{
private readonly string _name;
public SimpleTestAgent(string name)
{
this._name = name;
}
public override string Name => this._name;
public override AgentThread GetNewThread() => new SimpleTestAgentThread();
public override AgentThread DeserializeThread(System.Text.Json.JsonElement serializedThread,
System.Text.Json.JsonSerializerOptions? jsonSerializerOptions = null) => new SimpleTestAgentThread();
public override Task<AgentRunResponse> RunAsync(
IEnumerable<ChatMessage> messages,
AgentThread? thread = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default)
{
var lastMessage = messages.LastOrDefault();
var responseMessage = new ChatMessage(ChatRole.Assistant, $"Echo: {lastMessage?.Text ?? "no message"}");
return Task.FromResult(new AgentRunResponse(responseMessage));
}
public override async IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
IEnumerable<ChatMessage> messages,
AgentThread? thread = null,
AgentRunOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await Task.Yield();
var lastMessage = messages.LastOrDefault();
var responseText = $"Echo: {lastMessage?.Text ?? "no message"}";
string messageId = Guid.NewGuid().ToString("N");
// Yield role first
yield return new AgentRunResponseUpdate(ChatRole.Assistant, this._name)
{
AuthorName = this._name,
MessageId = messageId
};
// Then yield content
yield return new AgentRunResponseUpdate(ChatRole.Assistant, responseText)
{
AuthorName = this._name,
MessageId = messageId
};
}
}
/// <summary>
/// Simple thread implementation for SimpleTestAgent.
/// </summary>
private sealed class SimpleTestAgentThread : InMemoryAgentThread
{
}
}