diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index d0fab6bf1dc..d6b664ceb85 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -159,7 +159,7 @@ - + all @@ -170,27 +170,27 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory.csproj b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory.csproj index ccb41befe91..5376b3c7cdc 100644 --- a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory.csproj +++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory.csproj @@ -53,7 +53,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -61,15 +61,15 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/AgentWithRAG_Step05_Neo4jGraphRAG.csproj b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/AgentWithRAG_Step05_Neo4jGraphRAG.csproj index a9d07b246e3..dd07c33f219 100644 --- a/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/AgentWithRAG_Step05_Neo4jGraphRAG.csproj +++ b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/AgentWithRAG_Step05_Neo4jGraphRAG.csproj @@ -27,7 +27,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -35,15 +35,15 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs index 2ba6472244e..91af566e0f0 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs @@ -458,13 +458,15 @@ public async Task RunAsync_NestedRun_RestoresOuterCurrentRunContextAsync() ItExpr.IsAny(), ItExpr.IsAny(), ItExpr.IsAny()) - .Returns(async () => - { - outerContextBeforeInnerRun = AIAgent.CurrentRunContext; - await innerAgentMock.Object.RunAsync("Inner request", innerSession); - outerContextAfterInnerRun = AIAgent.CurrentRunContext; - return new AgentResponse(new ChatMessage(ChatRole.Assistant, "Outer response")); - }); + .Returns(RunCoreAsync); + + async Task RunCoreAsync() + { + outerContextBeforeInnerRun = AIAgent.CurrentRunContext; + await innerAgentMock.Object.RunAsync("Inner request", innerSession); + outerContextAfterInnerRun = AIAgent.CurrentRunContext; + return new AgentResponse(new ChatMessage(ChatRole.Assistant, "Outer response")); + } // Act await outerAgentMock.Object.RunAsync("Outer request", outerSession); diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/MessageInjectingChatClientTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/MessageInjectingChatClientTests.cs index ce6f20681b8..3292f804773 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/MessageInjectingChatClientTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/MessageInjectingChatClientTests.cs @@ -10,6 +10,8 @@ using Moq; using Moq.Protected; +#pragma warning disable Moq1206 + namespace Microsoft.Agents.AI.UnitTests; /// @@ -176,18 +178,20 @@ public async Task RunAsync_LoopsInternally_WhenNoActionableFCCButPendingMessages It.IsAny>(), It.IsAny(), It.IsAny())) - .Returns(async (IEnumerable msgs, ChatOptions? _, CancellationToken ct) => + .Returns(GetResponseAsync); + + async Task GetResponseAsync(IEnumerable msgs, ChatOptions? _, CancellationToken ct) + { + serviceCallCount++; + if (serviceCallCount == 1) { - serviceCallCount++; - if (serviceCallCount == 1) - { - // First call — simulate that something enqueues a message (e.g., a provider or background task) - await injectorRef!.EnqueueMessagesAsync(sessionRef!, [new ChatMessage(ChatRole.User, "injected during first call")], ct); - } + // First call — simulate that something enqueues a message (e.g., a provider or background task) + await injectorRef!.EnqueueMessagesAsync(sessionRef!, [new ChatMessage(ChatRole.User, "injected during first call")], ct); + } - // Return a plain text response (no FunctionCallContent) to trigger the internal loop - return new ChatResponse([new(ChatRole.Assistant, $"response {serviceCallCount}")]); - }); + // Return a plain text response (no FunctionCallContent) to trigger the internal loop + return new ChatResponse([new(ChatRole.Assistant, $"response {serviceCallCount}")]); + } Mock mockChatHistoryProvider = new(null, null, null); mockChatHistoryProvider.SetupGet(p => p.StateKeys).Returns(["TestChatHistoryProvider"]); @@ -237,21 +241,23 @@ public async Task RunAsync_DoesNotLoopInternally_WhenActionableFCCPresentAsync() It.IsAny>(), It.IsAny(), It.IsAny())) - .Returns(async (IEnumerable msgs, ChatOptions? _, CancellationToken ct) => - { - serviceCallCount++; - if (serviceCallCount == 1) - { - // Enqueue a message during the first call - await injectorRef!.EnqueueMessagesAsync(sessionRef!, [new ChatMessage(ChatRole.User, "injected")], ct); - // Return a response with an actionable FunctionCallContent - return new ChatResponse([new(ChatRole.Assistant, - [new FunctionCallContent("call1", "myTool", new Dictionary())])]); - } + .Returns(GetResponseAsync); - // Subsequent calls return plain text (the FCC loop will call back after tool execution) - return new ChatResponse([new(ChatRole.Assistant, "final")]); - }); + async Task GetResponseAsync(IEnumerable msgs, ChatOptions? _, CancellationToken ct) + { + serviceCallCount++; + if (serviceCallCount == 1) + { + // Enqueue a message during the first call + await injectorRef!.EnqueueMessagesAsync(sessionRef!, [new ChatMessage(ChatRole.User, "injected")], ct); + // Return a response with an actionable FunctionCallContent + return new ChatResponse([new(ChatRole.Assistant, + [new FunctionCallContent("call1", "myTool", new Dictionary())])]); + } + + // Subsequent calls return plain text (the FCC loop will call back after tool execution) + return new ChatResponse([new(ChatRole.Assistant, "final")]); + } Mock mockChatHistoryProvider = new(null, null, null); mockChatHistoryProvider.SetupGet(p => p.StateKeys).Returns(["TestChatHistoryProvider"]); @@ -307,26 +313,28 @@ public async Task RunAsync_ActionableFCCOnLaterIteration_AggregatesUsageAcrossIn It.IsAny>(), It.IsAny(), It.IsAny())) - .Returns(async (IEnumerable msgs, ChatOptions? _, CancellationToken ct) => + .Returns(GetResponseAsync); + + async Task GetResponseAsync(IEnumerable msgs, ChatOptions? _, CancellationToken ct) + { + serviceCallCount++; + if (serviceCallCount == 1) { - serviceCallCount++; - if (serviceCallCount == 1) - { - await injectorRef!.EnqueueMessagesAsync(sessionRef!, [new ChatMessage(ChatRole.User, "injected")], ct); - return new ChatResponse([new(ChatRole.Assistant, "queued")]) { Usage = CreateUsageForCall(serviceCallCount) }; - } + await injectorRef!.EnqueueMessagesAsync(sessionRef!, [new ChatMessage(ChatRole.User, "injected")], ct); + return new ChatResponse([new(ChatRole.Assistant, "queued")]) { Usage = CreateUsageForCall(serviceCallCount) }; + } - if (serviceCallCount == 2) + if (serviceCallCount == 2) + { + return new ChatResponse([new(ChatRole.Assistant, + [new FunctionCallContent("call1", "myTool", new Dictionary())])]) { - return new ChatResponse([new(ChatRole.Assistant, - [new FunctionCallContent("call1", "myTool", new Dictionary())])]) - { - Usage = CreateUsageForCall(serviceCallCount) - }; - } + Usage = CreateUsageForCall(serviceCallCount) + }; + } - return new ChatResponse([new(ChatRole.Assistant, "final")]) { Usage = CreateUsageForCall(serviceCallCount) }; - }); + return new ChatResponse([new(ChatRole.Assistant, "final")]) { Usage = CreateUsageForCall(serviceCallCount) }; + } var tool = AIFunctionFactory.Create(() => "tool result", "myTool", "A test tool"); ChatClientAgent agent = new(mockService.Object, options: new() @@ -370,20 +378,22 @@ public async Task RunAsync_LoopsInternally_WhenOnlyInformationalOnlyFCCAndPendin It.IsAny>(), It.IsAny(), It.IsAny())) - .Returns(async (IEnumerable msgs, ChatOptions? _, CancellationToken ct) => - { - serviceCallCount++; - if (serviceCallCount == 1) - { - // Enqueue a message during the first call - await injectorRef!.EnqueueMessagesAsync(sessionRef!, [new ChatMessage(ChatRole.User, "injected")], ct); - // Return a response with InformationalOnly FCC (not actionable) - return new ChatResponse([new(ChatRole.Assistant, - [new FunctionCallContent("call1", "myTool", new Dictionary()) { InformationalOnly = true }])]); - } + .Returns(GetResponseAsync); - return new ChatResponse([new(ChatRole.Assistant, "final")]); - }); + async Task GetResponseAsync(IEnumerable msgs, ChatOptions? _, CancellationToken ct) + { + serviceCallCount++; + if (serviceCallCount == 1) + { + // Enqueue a message during the first call + await injectorRef!.EnqueueMessagesAsync(sessionRef!, [new ChatMessage(ChatRole.User, "injected")], ct); + // Return a response with InformationalOnly FCC (not actionable) + return new ChatResponse([new(ChatRole.Assistant, + [new FunctionCallContent("call1", "myTool", new Dictionary()) { InformationalOnly = true }])]); + } + + return new ChatResponse([new(ChatRole.Assistant, "final")]); + } Mock mockChatHistoryProvider = new(null, null, null); mockChatHistoryProvider.SetupGet(p => p.StateKeys).Returns(["TestChatHistoryProvider"]); @@ -434,24 +444,26 @@ public async Task RunAsync_PropagatesConversationId_AcrossInternalLoopIterations It.IsAny>(), It.IsAny(), It.IsAny())) - .Returns(async (IEnumerable _, ChatOptions? opts, CancellationToken ct) => - { - serviceCallCount++; - capturedConversationIds.Add(opts?.ConversationId); + .Returns(GetResponseAsync); + + async Task GetResponseAsync(IEnumerable _, ChatOptions? opts, CancellationToken ct) + { + serviceCallCount++; + capturedConversationIds.Add(opts?.ConversationId); - if (serviceCallCount == 1) + if (serviceCallCount == 1) + { + // First call: inject a message and return a ConversationId + await injectorRef!.EnqueueMessagesAsync(sessionRef!, [new ChatMessage(ChatRole.User, "injected")], ct); + return new ChatResponse([new(ChatRole.Assistant, "first response")]) { - // First call: inject a message and return a ConversationId - await injectorRef!.EnqueueMessagesAsync(sessionRef!, [new ChatMessage(ChatRole.User, "injected")], ct); - return new ChatResponse([new(ChatRole.Assistant, "first response")]) - { - ConversationId = "conv-123", - }; - } + ConversationId = "conv-123", + }; + } - // Second call (from loop): should have the propagated ConversationId - return new ChatResponse([new(ChatRole.Assistant, "second response")]); - }); + // Second call (from loop): should have the propagated ConversationId + return new ChatResponse([new(ChatRole.Assistant, "second response")]); + } ChatClientAgent agent = new(mockService.Object, options: new() { @@ -491,25 +503,27 @@ public async Task RunAsync_DeliversInjectedMessages_AfterSessionSerializationRou It.IsAny>(), It.IsAny(), It.IsAny())) - .Returns(async (IEnumerable msgs, ChatOptions? _, CancellationToken ct) => + .Returns(GetResponseAsync); + + async Task GetResponseAsync(IEnumerable msgs, ChatOptions? _, CancellationToken ct) + { + if (runCount == 1) { - if (runCount == 1) - { - capturedMessagesFirstRun.AddRange(msgs); + capturedMessagesFirstRun.AddRange(msgs); - // Inject a message during the first run — this will remain pending (not drained) - // because we return an actionable FCC that causes the parent loop to take over. - await injectorRef!.EnqueueMessagesAsync(sessionRef!, [new ChatMessage(ChatRole.User, "injected before serialization")], ct); + // Inject a message during the first run — this will remain pending (not drained) + // because we return an actionable FCC that causes the parent loop to take over. + await injectorRef!.EnqueueMessagesAsync(sessionRef!, [new ChatMessage(ChatRole.User, "injected before serialization")], ct); - // Return actionable FCC so the injection loop does NOT drain the message - return new ChatResponse([new(ChatRole.Assistant, - [new FunctionCallContent("call1", "myTool", new Dictionary())])]); - } + // Return actionable FCC so the injection loop does NOT drain the message + return new ChatResponse([new(ChatRole.Assistant, + [new FunctionCallContent("call1", "myTool", new Dictionary())])]); + } - // Second run (after deserialization) — capture what messages come through - capturedMessagesSecondRun.AddRange(msgs); - return new ChatResponse([new(ChatRole.Assistant, "final response")]); - }); + // Second run (after deserialization) — capture what messages come through + capturedMessagesSecondRun.AddRange(msgs); + return new ChatResponse([new(ChatRole.Assistant, "final response")]); + } Mock mockChatHistoryProvider = new(null, null, null); mockChatHistoryProvider.SetupGet(p => p.StateKeys).Returns(["TestChatHistoryProvider"]); @@ -620,20 +634,22 @@ public async Task RunAsync_LoopsInternally_AggregatesUsageAcrossIterationsAsync( It.IsAny>(), It.IsAny(), It.IsAny())) - .Returns(async (IEnumerable msgs, ChatOptions? _, CancellationToken ct) => + .Returns(GetResponseAsync); + + async Task GetResponseAsync(IEnumerable msgs, ChatOptions? _, CancellationToken ct) + { + serviceCallCount++; + if (serviceCallCount < 3) { - serviceCallCount++; - if (serviceCallCount < 3) - { - // Enqueue a message so the injection loop runs again. - await injectorRef!.EnqueueMessagesAsync(sessionRef!, [new ChatMessage(ChatRole.User, $"injected {serviceCallCount}")], ct); - } + // Enqueue a message so the injection loop runs again. + await injectorRef!.EnqueueMessagesAsync(sessionRef!, [new ChatMessage(ChatRole.User, $"injected {serviceCallCount}")], ct); + } - return new ChatResponse([new(ChatRole.Assistant, $"response {serviceCallCount}")]) - { - Usage = CreateUsageForCall(serviceCallCount), - }; - }); + return new ChatResponse([new(ChatRole.Assistant, $"response {serviceCallCount}")]) + { + Usage = CreateUsageForCall(serviceCallCount), + }; + } ChatClientAgent agent = new(mockService.Object, options: new() { @@ -674,21 +690,23 @@ public async Task RunAsync_InnerClientReturnsDerivedResponse_PreservesRuntimeTyp It.IsAny>(), It.IsAny(), It.IsAny())) - .Returns(async (IEnumerable msgs, ChatOptions? _, CancellationToken ct) => + .Returns(GetResponseAsync); + + async Task GetResponseAsync(IEnumerable msgs, ChatOptions? _, CancellationToken ct) + { + serviceCallCount++; + if (serviceCallCount < 3) { - serviceCallCount++; - if (serviceCallCount < 3) - { - // Enqueue a message so the injection loop runs again. - await injectorRef!.EnqueueMessagesAsync(sessionRef!, [new ChatMessage(ChatRole.User, $"injected {serviceCallCount}")], ct); - } + // Enqueue a message so the injection loop runs again. + await injectorRef!.EnqueueMessagesAsync(sessionRef!, [new ChatMessage(ChatRole.User, $"injected {serviceCallCount}")], ct); + } - return new TestDerivedChatResponse([new(ChatRole.Assistant, $"response {serviceCallCount}")]) - { - DerivedState = $"call {serviceCallCount}", - Usage = new UsageDetails { InputTokenCount = serviceCallCount, OutputTokenCount = serviceCallCount * 10 }, - }; - }); + return new TestDerivedChatResponse([new(ChatRole.Assistant, $"response {serviceCallCount}")]) + { + DerivedState = $"call {serviceCallCount}", + Usage = new UsageDetails { InputTokenCount = serviceCallCount, OutputTokenCount = serviceCallCount * 10 }, + }; + } ChatClientAgent agent = new(mockService.Object, options: new() { diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/FunctionInvocationDelegatingAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/FunctionInvocationDelegatingAgentTests.cs index 68228155a77..d39edde983d 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/FunctionInvocationDelegatingAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/FunctionInvocationDelegatingAgentTests.cs @@ -8,6 +8,8 @@ using Microsoft.Extensions.AI; using Moq; +#pragma warning disable Moq1206 + namespace Microsoft.Agents.AI.UnitTests; /// @@ -467,16 +469,19 @@ public async Task RunAsync_DirectFunctionInvocation_MiddlewareHandlesNullCurrent // Setup mock to directly invoke the function (bypassing FunctionInvokingChatClient) mockChatClient.Setup(c => c.GetResponseAsync(It.IsAny>(), It.IsAny(), It.IsAny())) - .Returns, ChatOptions, CancellationToken>(async (messages, options, ct) => + .Returns(GetResponseAsync); + + async Task GetResponseAsync(IEnumerable messages, ChatOptions? options, CancellationToken ct) + { + // Directly invoke the function to simulate null CurrentContext scenario + if (options?.Tools?.FirstOrDefault() is AIFunction function) { - // Directly invoke the function to simulate null CurrentContext scenario - if (options?.Tools?.FirstOrDefault() is AIFunction function) - { - executionOrder.Add("Direct-Function-Invocation"); - await function.InvokeAsync([], ct); - } - return new ChatResponse([new ChatMessage(ChatRole.Assistant, "Response after direct invocation")]); - }); + executionOrder.Add("Direct-Function-Invocation"); + await function.InvokeAsync([], ct); + } + + return new ChatResponse([new ChatMessage(ChatRole.Assistant, "Response after direct invocation")]); + } var innerAgent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/BackgroundAgents/BackgroundAgentsProviderTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/BackgroundAgents/BackgroundAgentsProviderTests.cs index 4ead3bfb1cc..ceb07610908 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/BackgroundAgents/BackgroundAgentsProviderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/BackgroundAgents/BackgroundAgentsProviderTests.cs @@ -10,6 +10,8 @@ using Moq; using Moq.Protected; +#pragma warning disable Moq1206 + namespace Microsoft.Agents.AI.UnitTests; /// @@ -1743,11 +1745,7 @@ private static AIAgent CreateMockAgentWithGatedSession(string name, Task session .Setup>( "CreateSessionCoreAsync", ItExpr.IsAny()) - .Returns(async () => - { - await sessionGate; - return new ChatClientAgentSession(); - }); + .Returns(CreateSessionCoreAsync); mock.Protected() .Setup>( "RunCoreAsync", @@ -1757,6 +1755,12 @@ private static AIAgent CreateMockAgentWithGatedSession(string name, Task session ItExpr.IsAny()) .Returns(callback); return mock.Object; + + async ValueTask CreateSessionCoreAsync() + { + await sessionGate; + return new ChatClientAgentSession(); + } } private static async Task<(IEnumerable Tools, BackgroundAgentsProvider Provider, AgentSession Session)> CreateToolsWithSessionAsync(AIAgent agent)