From 90a3e5de47cf48dfdef25c00c49cf56e0c80df8e Mon Sep 17 00:00:00 2001
From: westey <164392973+westey-m@users.noreply.github.com>
Date: Wed, 3 Jun 2026 10:09:39 +0100
Subject: [PATCH] .NET: Add ILoggerFactory and IServiceProvider to HarnessAgent
constructor (#6273)
* Add ILoggerFactory and IServiceProvider to HarnessAgent constructor
Add optional ILoggerFactory and IServiceProvider parameters to the
HarnessAgent constructor and AsHarnessAgent extension method, passing
them to all downstream components that accept them:
- FunctionInvokingChatClient (via UseFunctionInvocation)
- CompactionProvider
- AgentSkillsProvider
- ChatClientAgent (via BuildAIAgent)
- AIAgentBuilder.Build()
Closes #6103
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Improve tests to verify ILoggerFactory and IServiceProvider propagation
- Add test verifying ILoggerFactory.CreateLogger() is called by
downstream components (CompactionProvider, AgentSkillsProvider)
- Add test verifying IServiceProvider is queried during pipeline build
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../ChatClientHarnessExtensions.cs | 14 +-
.../HarnessAgent.cs | 37 +++--
.../HarnessAgentTests.cs | 128 ++++++++++++++++++
3 files changed, 164 insertions(+), 15 deletions(-)
diff --git a/dotnet/src/Microsoft.Agents.AI.Harness/ChatClientHarnessExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Harness/ChatClientHarnessExtensions.cs
index 1a55624f3d..be66e9e635 100644
--- a/dotnet/src/Microsoft.Agents.AI.Harness/ChatClientHarnessExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Harness/ChatClientHarnessExtensions.cs
@@ -1,7 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Agents.AI;
+using Microsoft.Extensions.Logging;
using Microsoft.Shared.DiagnosticIds;
namespace Microsoft.Extensions.AI;
@@ -32,11 +34,19 @@ public static class ChatClientHarnessExtensions
/// additional context providers, and chat history provider.
/// When , the agent uses built-in default settings.
///
+ ///
+ /// Optional logger factory for creating loggers used by the agent and its components.
+ ///
+ ///
+ /// Optional service provider for resolving dependencies required by AI functions and other agent components.
+ ///
/// A new instance.
public static HarnessAgent AsHarnessAgent(
this IChatClient chatClient,
int maxContextWindowTokens,
int maxOutputTokens,
- HarnessAgentOptions? options = null) =>
- new(chatClient, maxContextWindowTokens, maxOutputTokens, options);
+ HarnessAgentOptions? options = null,
+ ILoggerFactory? loggerFactory = null,
+ IServiceProvider? services = null) =>
+ new(chatClient, maxContextWindowTokens, maxOutputTokens, options, loggerFactory, services);
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgent.cs b/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgent.cs
index ef1af05513..139f47db3c 100644
--- a/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgent.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgent.cs
@@ -10,6 +10,7 @@ using Microsoft.Agents.AI.Compaction;
using Microsoft.Agents.AI.Tools.Shell;
#endif
using Microsoft.Extensions.AI;
+using Microsoft.Extensions.Logging;
using Microsoft.Shared.DiagnosticIds;
using Microsoft.Shared.Diagnostics;
@@ -105,6 +106,12 @@ public sealed class HarnessAgent : DelegatingAIAgent
/// additional context providers, and chat history provider.
/// When , the agent uses built-in default settings.
///
+ ///
+ /// Optional logger factory for creating loggers used by the agent and its components.
+ ///
+ ///
+ /// Optional service provider for resolving dependencies required by AI functions and other agent components.
+ ///
///
/// is .
///
@@ -112,18 +119,20 @@ public sealed class HarnessAgent : DelegatingAIAgent
/// is not positive, or
/// is negative or greater than or equal to .
///
- public HarnessAgent(IChatClient chatClient, int maxContextWindowTokens, int maxOutputTokens, HarnessAgentOptions? options = null)
+ public HarnessAgent(IChatClient chatClient, int maxContextWindowTokens, int maxOutputTokens, HarnessAgentOptions? options = null, ILoggerFactory? loggerFactory = null, IServiceProvider? services = null)
: base(BuildAgent(
Throw.IfNull(chatClient),
maxContextWindowTokens,
maxOutputTokens,
- options))
+ options,
+ loggerFactory,
+ services))
{
}
- private static AIAgent BuildAgent(IChatClient chatClient, int maxContextWindowTokens, int maxOutputTokens, HarnessAgentOptions? options)
+ private static AIAgent BuildAgent(IChatClient chatClient, int maxContextWindowTokens, int maxOutputTokens, HarnessAgentOptions? options, ILoggerFactory? loggerFactory, IServiceProvider? services)
{
- ChatClientAgent innerAgent = BuildInnerAgent(chatClient, maxContextWindowTokens, maxOutputTokens, options);
+ ChatClientAgent innerAgent = BuildInnerAgent(chatClient, maxContextWindowTokens, maxOutputTokens, options, loggerFactory, services);
AIAgentBuilder builder = innerAgent.AsBuilder();
@@ -137,10 +146,10 @@ public sealed class HarnessAgent : DelegatingAIAgent
builder.UseOpenTelemetry(sourceName: options?.OpenTelemetrySourceName);
}
- return builder.Build();
+ return builder.Build(services);
}
- private static ChatClientAgent BuildInnerAgent(IChatClient chatClient, int maxContextWindowTokens, int maxOutputTokens, HarnessAgentOptions? options)
+ private static ChatClientAgent BuildInnerAgent(IChatClient chatClient, int maxContextWindowTokens, int maxOutputTokens, HarnessAgentOptions? options, ILoggerFactory? loggerFactory, IServiceProvider? services)
{
var compactionStrategy = new ContextWindowCompactionStrategy(
maxContextWindowTokens: maxContextWindowTokens,
@@ -165,13 +174,13 @@ public sealed class HarnessAgent : DelegatingAIAgent
ChatOptions chatOptions = BuildChatOptions(options, instructions, maxOutputTokens);
- var compactionProvider = new CompactionProvider(compactionStrategy);
+ var compactionProvider = new CompactionProvider(compactionStrategy, loggerFactory: loggerFactory);
- IEnumerable contextProviders = BuildContextProviders(options);
+ IEnumerable contextProviders = BuildContextProviders(options, loggerFactory);
return chatClient
.AsBuilder()
- .UseFunctionInvocation(configure: options?.MaximumIterationsPerRequest is int maxIterations
+ .UseFunctionInvocation(loggerFactory, configure: options?.MaximumIterationsPerRequest is int maxIterations
? ficc => ficc.MaximumIterationsPerRequest = maxIterations
: null)
.UseMessageInjection()
@@ -189,7 +198,9 @@ public sealed class HarnessAgent : DelegatingAIAgent
RequirePerServiceCallChatHistoryPersistence = true,
WarnOnChatHistoryProviderConflict = false,
ThrowOnChatHistoryProviderConflict = false,
- });
+ },
+ loggerFactory,
+ services);
}
private static ChatOptions BuildChatOptions(HarnessAgentOptions? options, string instructions, int maxOutputTokens)
@@ -215,7 +226,7 @@ public sealed class HarnessAgent : DelegatingAIAgent
return result;
}
- private static List BuildContextProviders(HarnessAgentOptions? options)
+ private static List BuildContextProviders(HarnessAgentOptions? options, ILoggerFactory? loggerFactory)
{
var providers = new List();
@@ -255,8 +266,8 @@ public sealed class HarnessAgent : DelegatingAIAgent
if (options?.DisableAgentSkillsProvider is not true)
{
AgentSkillsProvider skillsProvider = options?.AgentSkillsSource is AgentSkillsSource source
- ? new AgentSkillsProvider(source)
- : new AgentSkillsProvider(Directory.GetCurrentDirectory());
+ ? new AgentSkillsProvider(source, loggerFactory: loggerFactory)
+ : new AgentSkillsProvider(Directory.GetCurrentDirectory(), loggerFactory: loggerFactory);
providers.Add(skillsProvider);
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentTests.cs
index 4f08209bd8..2711fa1458 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentTests.cs
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
using Microsoft.Agents.AI.Tools.Shell;
#endif
using Microsoft.Extensions.AI;
+using Microsoft.Extensions.Logging;
using Moq;
namespace Microsoft.Agents.AI.UnitTests;
@@ -1460,4 +1461,131 @@ public class HarnessAgentTests
#endregion
#endif
+
+ #region LoggerFactory and ServiceProvider
+
+ ///
+ /// Verify that the constructor succeeds when loggerFactory is provided.
+ ///
+ [Fact]
+ public void Constructor_SucceedsWithLoggerFactory()
+ {
+ // Arrange
+ var chatClient = new Mock().Object;
+ var loggerFactory = new Mock().Object;
+
+ // Act
+ var agent = new HarnessAgent(chatClient, TestMaxContextWindowTokens, TestMaxOutputTokens, CreateAllDisabledOptions(), loggerFactory);
+
+ // Assert
+ Assert.NotNull(agent);
+ }
+
+ ///
+ /// Verify that the constructor succeeds when serviceProvider is provided.
+ ///
+ [Fact]
+ public void Constructor_SucceedsWithServiceProvider()
+ {
+ // Arrange
+ var chatClient = new Mock().Object;
+ var services = new Mock().Object;
+
+ // Act
+ var agent = new HarnessAgent(chatClient, TestMaxContextWindowTokens, TestMaxOutputTokens, CreateAllDisabledOptions(), services: services);
+
+ // Assert
+ Assert.NotNull(agent);
+ }
+
+ ///
+ /// Verify that the constructor succeeds when both loggerFactory and serviceProvider are provided.
+ ///
+ [Fact]
+ public void Constructor_SucceedsWithLoggerFactoryAndServiceProvider()
+ {
+ // Arrange
+ var chatClient = new Mock().Object;
+ var loggerFactory = new Mock().Object;
+ var services = new Mock().Object;
+
+ // Act
+ var agent = new HarnessAgent(chatClient, TestMaxContextWindowTokens, TestMaxOutputTokens, CreateAllDisabledOptions(), loggerFactory, services);
+
+ // Assert
+ Assert.NotNull(agent);
+ }
+
+ ///
+ /// Verify that AsHarnessAgent extension method accepts loggerFactory and serviceProvider.
+ ///
+ [Fact]
+ public void AsHarnessAgent_SucceedsWithLoggerFactoryAndServiceProvider()
+ {
+ // Arrange
+ var chatClient = new Mock().Object;
+ var loggerFactory = new Mock().Object;
+ var services = new Mock().Object;
+
+ // Act
+ var agent = chatClient.AsHarnessAgent(TestMaxContextWindowTokens, TestMaxOutputTokens, CreateAllDisabledOptions(), loggerFactory, services);
+
+ // Assert
+ Assert.NotNull(agent);
+ }
+
+ ///
+ /// Verify that ILoggerFactory is threaded to downstream components by confirming CreateLogger is called.
+ ///
+ [Fact]
+ public void Constructor_LoggerFactoryIsUsedByDownstreamComponents()
+ {
+ // Arrange
+ var chatClient = new Mock().Object;
+ var mockLoggerFactory = new Mock();
+ mockLoggerFactory
+ .Setup(lf => lf.CreateLogger(It.IsAny()))
+ .Returns(new Mock().Object);
+
+ // Act — use options that leave CompactionProvider and AgentSkillsProvider enabled
+ var options = new HarnessAgentOptions
+ {
+ DisableToolApproval = true,
+ DisableOpenTelemetry = true,
+ DisableFileMemory = true,
+ DisableFileAccess = true,
+ DisableWebSearch = true,
+ DisableTodoProvider = true,
+ DisableAgentModeProvider = true,
+ };
+ var agent = new HarnessAgent(chatClient, TestMaxContextWindowTokens, TestMaxOutputTokens, options, mockLoggerFactory.Object);
+
+ // Assert — CreateLogger should have been called by one or more downstream components
+ Assert.NotNull(agent);
+ mockLoggerFactory.Verify(lf => lf.CreateLogger(It.IsAny()), Times.AtLeastOnce());
+ }
+
+ ///
+ /// Verify that IServiceProvider is propagated through the agent pipeline by confirming
+ /// it is queried during agent construction.
+ ///
+ [Fact]
+ public void Constructor_ServiceProviderIsQueriedDuringBuild()
+ {
+ // Arrange
+ var chatClient = new Mock().Object;
+ var mockServices = new Mock();
+ mockServices
+ .Setup(sp => sp.GetService(It.IsAny()))
+ .Returns(null!);
+
+ // Act
+ var agent = new HarnessAgent(chatClient, TestMaxContextWindowTokens, TestMaxOutputTokens, CreateAllDisabledOptions(), services: mockServices.Object);
+
+ // Assert — the service provider should have been queried during pipeline construction
+ Assert.NotNull(agent);
+ mockServices.Verify(sp => sp.GetService(It.IsAny()), Times.AtLeastOnce());
+ }
+
+ #endregion
}