// Copyright (c) Microsoft Corporation. All rights reserved. // TerminationConditionTests.cs using FluentAssertions; using Microsoft.AutoGen.AgentChat.Abstractions; using Microsoft.AutoGen.AgentChat.Terminations; using Microsoft.Extensions.AI; using Xunit; namespace Microsoft.AutoGen.AgentChat.Tests; [Trait("Category", "UnitV2")] public static class TerminationExtensions { public static async Task InvokeExpectingNullAsync(this TTermination termination, IList messages, bool reset = true) where TTermination : ITerminationCondition { (await termination.CheckAndUpdateAsync(messages)).Should().BeNull(); termination.IsTerminated.Should().BeFalse(); if (reset) { termination.Reset(); } } private static readonly HashSet AnonymousTerminationConditions = ["CombinerCondition", nameof(ITerminationCondition)]; public static async Task InvokeExpectingStopAsync(this TTermination termination, IList messages, bool reset = true) where TTermination : ITerminationCondition { StopMessage? stopMessage = await termination.CheckAndUpdateAsync(messages); stopMessage.Should().NotBeNull(); string name = typeof(TTermination).Name; if (!AnonymousTerminationConditions.Contains(name)) { stopMessage!.Source.Should().Be(typeof(TTermination).Name); } termination.IsTerminated.Should().BeTrue(); if (reset) { termination.Reset(); } } public static async Task InvokeExpectingFailureAsync(this TTermination termination, IList messages, bool reset = true) where TTermination : ITerminationCondition { Func failureAction = () => termination.CheckAndUpdateAsync(messages).AsTask(); await failureAction.Should().ThrowAsync(); termination.IsTerminated.Should().BeTrue(); if (reset) { termination.Reset(); } } } public class TerminationConditionTests { [Fact] public async Task Test_HandoffTermination() { HandoffTermination termination = new("target"); termination.IsTerminated.Should().BeFalse(); TextMessage textMessage = new() { Content = "Hello", Source = "user" }; HandoffMessage targetHandoffMessage = new() { Target = "target", Source = "user", Context = "Hello" }; HandoffMessage otherHandoffMessage = new() { Target = "another", Source = "user", Context = "Hello" }; await termination.InvokeExpectingNullAsync([]); await termination.InvokeExpectingNullAsync([textMessage]); await termination.InvokeExpectingStopAsync([targetHandoffMessage]); await termination.InvokeExpectingNullAsync([otherHandoffMessage]); await termination.InvokeExpectingStopAsync([textMessage, targetHandoffMessage], reset: false); await termination.InvokeExpectingFailureAsync([], reset: false); termination.Reset(); termination.IsTerminated.Should().BeFalse(); } [Fact] public async Task StopMessageTermination() { StopMessageTermination termination = new(); termination.IsTerminated.Should().BeFalse(); TextMessage textMessage = new() { Content = "Hello", Source = "user" }; TextMessage otherMessage = new() { Content = "World", Source = "aser" }; StopMessage stopMessage = new() { Content = "Stop", Source = "user" }; await termination.InvokeExpectingNullAsync([]); await termination.InvokeExpectingNullAsync([textMessage]); await termination.InvokeExpectingStopAsync([stopMessage]); await termination.InvokeExpectingNullAsync([textMessage, otherMessage]); await termination.InvokeExpectingStopAsync([textMessage, stopMessage], reset: false); await termination.InvokeExpectingFailureAsync([], reset: false); termination.Reset(); termination.IsTerminated.Should().BeFalse(); } [Fact] public async Task Test_TextMesssageTermination() { TextMessageTermination termination = new(); termination.IsTerminated.Should().BeFalse(); TextMessage userMessage = new() { Content = "Hello", Source = "user" }; TextMessage agentMessage = new() { Content = "World", Source = "agent" }; StopMessage stopMessage = new() { Content = "Stop", Source = "user" }; await termination.InvokeExpectingNullAsync([]); await termination.InvokeExpectingStopAsync([userMessage]); await termination.InvokeExpectingStopAsync([agentMessage]); await termination.InvokeExpectingNullAsync([stopMessage]); termination = new("user"); await termination.InvokeExpectingNullAsync([agentMessage]); await termination.InvokeExpectingNullAsync([stopMessage]); await termination.InvokeExpectingStopAsync([userMessage], reset: false); await termination.InvokeExpectingFailureAsync([], reset: false); termination.Reset(); termination.IsTerminated.Should().BeFalse(); } [Fact] public async Task MaxMessageTermination() { MaxMessageTermination termination = new(2); termination.IsTerminated.Should().BeFalse(); TextMessage textMessage = new() { Content = "Hello", Source = "user" }; TextMessage otherMessage = new() { Content = "World", Source = "agent" }; UserInputRequestedEvent uiRequest = new() { Source = "agent", RequestId = "1" }; await termination.InvokeExpectingNullAsync([]); await termination.InvokeExpectingNullAsync([textMessage]); await termination.InvokeExpectingStopAsync([textMessage, otherMessage]); await termination.InvokeExpectingNullAsync([textMessage, uiRequest]); termination = new(2, includeAgentEvent: true); await termination.InvokeExpectingStopAsync([textMessage, uiRequest], reset: false); await termination.InvokeExpectingFailureAsync([], reset: false); termination.Reset(); termination.IsTerminated.Should().BeFalse(); } [Fact] public async Task Test_TextMentionTermination() { TextMentionTermination termination = new("stop"); termination.IsTerminated.Should().BeFalse(); TextMessage textMessage = new() { Content = "Hello", Source = "user" }; TextMessage userStopMessage = new() { Content = "stop", Source = "user" }; TextMessage agentStopMessage = new() { Content = "stop", Source = "agent" }; await termination.InvokeExpectingNullAsync([]); await termination.InvokeExpectingNullAsync([textMessage]); await termination.InvokeExpectingStopAsync([userStopMessage]); termination = new("stop", sources: ["agent"]); await termination.InvokeExpectingNullAsync([textMessage]); await termination.InvokeExpectingNullAsync([userStopMessage]); await termination.InvokeExpectingStopAsync([agentStopMessage], reset: false); await termination.InvokeExpectingFailureAsync([], reset: false); termination.Reset(); termination.IsTerminated.Should().BeFalse(); } [Fact] public async Task Text_TokenUsageTermination() { TokenUsageTermination termination = new(10); termination.IsTerminated.Should().BeFalse(); RequestUsage usage_10_10 = new() { CompletionTokens = 10, PromptTokens = 10 }; RequestUsage usage_01_01 = new() { CompletionTokens = 1, PromptTokens = 1 }; RequestUsage usage_05_00 = new() { CompletionTokens = 5, PromptTokens = 0 }; RequestUsage usage_00_05 = new() { CompletionTokens = 0, PromptTokens = 5 }; await termination.InvokeExpectingNullAsync([]); await termination.InvokeExpectingStopAsync([ new TextMessage { Content = "Hello", Source = "user", ModelUsage = usage_10_10 }, ]); await termination.InvokeExpectingNullAsync([ new TextMessage { Content = "Hello", Source = "user", ModelUsage = usage_01_01 }, new TextMessage { Content = "World", Source = "agent", ModelUsage = usage_01_01 }, ]); await termination.InvokeExpectingStopAsync([ new TextMessage { Content = "Hello", Source = "user", ModelUsage = usage_05_00 }, new TextMessage { Content = "World", Source = "agent", ModelUsage = usage_00_05 }, ], reset: false); await termination.InvokeExpectingFailureAsync([], reset: false); termination.Reset(); termination.IsTerminated.Should().BeFalse(); } public class AgentTextEvent : AgentEvent { public required string Content { get; set; } public override Extensions.AI.ChatMessage ToCompletionClientMessage(ChatRole role) { return new Extensions.AI.ChatMessage(ChatRole.Assistant, this.Content); } } [Fact] public async Task Text_Termination_AndCombinator() { ITerminationCondition lhsClause = new MaxMessageTermination(2); ITerminationCondition rhsClause = new TextMentionTermination("stop"); ITerminationCondition termination = lhsClause & rhsClause; termination.IsTerminated.Should().BeFalse(); TextMessage userMessage = new() { Content = "Hello", Source = "user" }; AgentTextEvent agentMessage = new() { Content = "World", Source = "agent" }; TextMessage userStopMessage = new() { Content = "stop", Source = "user" }; await termination.InvokeExpectingNullAsync([]); await termination.InvokeExpectingNullAsync([userMessage]); await termination.InvokeExpectingNullAsync([userMessage, agentMessage], reset: false); lhsClause.IsTerminated.Should().BeFalse(); rhsClause.IsTerminated.Should().BeFalse(); await termination.InvokeExpectingStopAsync([userStopMessage], reset: false); lhsClause.IsTerminated.Should().BeTrue(); rhsClause.IsTerminated.Should().BeTrue(); termination.IsTerminated.Should().BeTrue(); await termination.InvokeExpectingFailureAsync([], reset: false); lhsClause.IsTerminated.Should().BeTrue(); rhsClause.IsTerminated.Should().BeTrue(); termination.IsTerminated.Should().BeTrue(); termination.Reset(); termination.IsTerminated.Should().BeFalse(); await termination.InvokeExpectingNullAsync([userMessage, agentMessage], reset: false); lhsClause.IsTerminated.Should().BeFalse(); rhsClause.IsTerminated.Should().BeFalse(); await termination.InvokeExpectingNullAsync([userMessage], reset: false); lhsClause.IsTerminated.Should().BeTrue(); rhsClause.IsTerminated.Should().BeFalse(); termination.IsTerminated.Should().BeFalse(); await termination.InvokeExpectingNullAsync([userMessage], reset: false); lhsClause.IsTerminated.Should().BeTrue(); rhsClause.IsTerminated.Should().BeFalse(); termination.IsTerminated.Should().BeFalse(); await termination.InvokeExpectingStopAsync([userStopMessage], reset: false); lhsClause.IsTerminated.Should().BeTrue(); rhsClause.IsTerminated.Should().BeTrue(); termination.IsTerminated.Should().BeTrue(); await termination.InvokeExpectingFailureAsync([], reset: false); lhsClause.IsTerminated.Should().BeTrue(); rhsClause.IsTerminated.Should().BeTrue(); termination.IsTerminated.Should().BeTrue(); termination.Reset(); termination.IsTerminated.Should().BeFalse(); await termination.InvokeExpectingNullAsync([agentMessage, userStopMessage], reset: false); lhsClause.IsTerminated.Should().BeFalse(); rhsClause.IsTerminated.Should().BeTrue(); termination.IsTerminated.Should().BeFalse(); await termination.InvokeExpectingStopAsync([userMessage], reset: false); lhsClause.IsTerminated.Should().BeTrue(); rhsClause.IsTerminated.Should().BeTrue(); termination.IsTerminated.Should().BeTrue(); await termination.InvokeExpectingFailureAsync([], reset: false); lhsClause.IsTerminated.Should().BeTrue(); rhsClause.IsTerminated.Should().BeTrue(); termination.IsTerminated.Should().BeTrue(); termination.Reset(); termination.IsTerminated.Should().BeFalse(); } [Fact] public async Task Test_Termination_OrCombiner() { ITerminationCondition lhsClause = new MaxMessageTermination(3); ITerminationCondition rhsClause = new TextMentionTermination("stop"); ITerminationCondition termination = lhsClause | rhsClause; termination.IsTerminated.Should().BeFalse(); TextMessage userMessage = new() { Content = "Hello", Source = "user" }; AgentTextEvent agentMessage = new() { Content = "World", Source = "agent" }; TextMessage userStopMessage = new() { Content = "stop", Source = "user" }; await termination.InvokeExpectingNullAsync([]); await termination.InvokeExpectingNullAsync([userMessage]); await termination.InvokeExpectingNullAsync([userMessage, agentMessage]); await termination.InvokeExpectingNullAsync([userMessage, agentMessage, userMessage], reset: false); lhsClause.IsTerminated.Should().BeFalse(); rhsClause.IsTerminated.Should().BeFalse(); termination.IsTerminated.Should().BeFalse(); termination.Reset(); termination.IsTerminated.Should().BeFalse(); await termination.InvokeExpectingStopAsync([userMessage, agentMessage, userStopMessage], reset: false); lhsClause.IsTerminated.Should().BeFalse(); rhsClause.IsTerminated.Should().BeTrue(); termination.IsTerminated.Should().BeTrue(); await termination.InvokeExpectingFailureAsync([], reset: false); lhsClause.IsTerminated.Should().BeFalse(); rhsClause.IsTerminated.Should().BeTrue(); termination.IsTerminated.Should().BeTrue(); termination.Reset(); termination.IsTerminated.Should().BeFalse(); await termination.InvokeExpectingStopAsync([userMessage, userMessage, userMessage], reset: false); lhsClause.IsTerminated.Should().BeTrue(); rhsClause.IsTerminated.Should().BeFalse(); termination.IsTerminated.Should().BeTrue(); await termination.InvokeExpectingFailureAsync([], reset: false); lhsClause.IsTerminated.Should().BeTrue(); rhsClause.IsTerminated.Should().BeFalse(); termination.IsTerminated.Should().BeTrue(); termination.Reset(); termination.IsTerminated.Should().BeFalse(); } [Fact] public async Task Test_TimeoutTermination() { TextMessage userMessage = new() { Content = "Hello", Source = "user" }; TimeoutTermination termination = new(0.15f); termination.IsTerminated.Should().BeFalse(); await termination.InvokeExpectingNullAsync([]); await Task.Delay(TimeSpan.FromSeconds(0.20f)); await termination.InvokeExpectingStopAsync([], reset: false); await termination.InvokeExpectingFailureAsync([], reset: false); termination.Reset(); termination.IsTerminated.Should().BeFalse(); await termination.InvokeExpectingNullAsync([userMessage]); await Task.Delay(TimeSpan.FromSeconds(0.20f)); await termination.InvokeExpectingStopAsync([], reset: false); } [Fact] public async Task Test_ExternalTermination() { ExternalTermination termination = new(); termination.IsTerminated.Should().BeFalse(); TextMessage userMessage = new() { Content = "Hello", Source = "user" }; await termination.InvokeExpectingNullAsync([]); await termination.InvokeExpectingNullAsync([userMessage]); termination.Set(); termination.IsTerminated.Should().BeFalse(); // We only terminate on the next check await termination.InvokeExpectingStopAsync([], reset: false); await termination.InvokeExpectingFailureAsync([], reset: false); termination.Reset(); termination.IsTerminated.Should().BeFalse(); await termination.InvokeExpectingNullAsync([userMessage]); } private ToolCallRequestEvent CreateFunctionRequest(string functionName, string id = "1", string arguments = "") { ToolCallRequestEvent result = new ToolCallRequestEvent { Source = "agent" }; result.Content.Add( new FunctionCall { Id = id, Name = functionName, Arguments = arguments, }); return result; } private ToolCallExecutionEvent CreateFunctionResponse(string functionName, string id = "1", string content = "") { ToolCallExecutionEvent result = new ToolCallExecutionEvent { Source = "agent" }; result.Content.Add( new FunctionExecutionResult { Id = id, Name = functionName, Content = content, }); return result; } [Fact] public async Task Test_FunctionCallTermination() { FunctionCallTermination termination = new("test_function"); termination.IsTerminated.Should().BeFalse(); TextMessage userMessage = new() { Content = "Hello", Source = "user" }; ToolCallRequestEvent toolCallRequest = CreateFunctionRequest("test_function"); ToolCallExecutionEvent testExecution = CreateFunctionResponse("test_function"); ToolCallExecutionEvent otherExecution = CreateFunctionResponse("other_function"); await termination.InvokeExpectingNullAsync([]); await termination.InvokeExpectingNullAsync([userMessage]); await termination.InvokeExpectingNullAsync([toolCallRequest]); await termination.InvokeExpectingNullAsync([otherExecution]); await termination.InvokeExpectingStopAsync([testExecution], reset: false); await termination.InvokeExpectingFailureAsync([], reset: false); termination.Reset(); termination.IsTerminated.Should().BeFalse(); } }