.NET: fix: pass IServiceProvider to ChatClientAgent in AddAIAgent overloads#4483
.NET: fix: pass IServiceProvider to ChatClientAgent in AddAIAgent overloads#4483max-montes wants to merge 1 commit intomicrosoft:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes a bug (#4453) where AddAIAgent overloads in AgentHostingServiceCollectionExtensions were not forwarding the IServiceProvider to ChatClientAgent, causing tools registered via dependency injection to fail to resolve their dependencies at invocation time.
Changes:
- Pass
services: spto each of the fourChatClientAgentconstructor calls inAddAIAgentoverloads so theFunctionInvokingChatClientmiddleware receives the application's service provider. - Add four regression tests (one per overload) and a
MockChatClienthelper class to verify that theFunctionInvokingChatClientis present in the chat client pipeline.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
dotnet/src/Microsoft.Agents.AI.Hosting/AgentHostingServiceCollectionExtensions.cs |
Adds services: sp to all four ChatClientAgent constructor calls to forward the DI container's IServiceProvider. |
dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/AgentHostingServiceCollectionExtensionsTests.cs |
Adds four regression tests and a MockChatClient class to verify the service provider is passed through each AddAIAgent overload. |
| // The FunctionInvokingChatClient in the pipeline should have been | ||
| // constructed with the application's IServiceProvider (not null). | ||
| var funcClient = agent!.ChatClient.GetService<FunctionInvokingChatClient>(); | ||
| Assert.NotNull(funcClient); |
There was a problem hiding this comment.
These tests assert only that FunctionInvokingChatClient exists in the pipeline (GetService<FunctionInvokingChatClient>() is not null), but this would be true even without the services: sp fix — the FunctionInvokingChatClient is always created by WithDefaultAgentMiddleware, regardless of whether a service provider is passed. The tests don't actually validate that the correct (application's) IServiceProvider was forwarded.
To be a meaningful regression test for issue #4453, the tests should verify that a service registered in the DI container is resolvable from the FunctionInvokingChatClient's service provider. For example, register a custom service, then retrieve the FunctionInvokingChatClient and verify its underlying IServiceProvider can resolve that service, or invoke a tool that depends on DI resolution and verify it succeeds.
cd40c83 to
8906f26
Compare
|
|
||
| if (spField is not null) | ||
| { | ||
| var innerProvider = spField.GetValue(funcClient) as IServiceProvider; | ||
| Assert.NotNull(innerProvider); | ||
| var marker = innerProvider!.GetService<IMarkerService>(); | ||
| Assert.NotNull(marker); | ||
| } |
There was a problem hiding this comment.
The if (spField is not null) guard means that if FunctionInvokingChatClient renames or removes the _serviceProvider field in a future version of Microsoft.Extensions.AI, the reflection lookup silently returns null, all the critical assertions inside the block are skipped, and the test passes without actually verifying anything. This undermines the purpose of the regression test.
Consider adding Assert.NotNull(spField) (or using Assert.Fail in the else branch) to ensure the test fails loudly if the internal field disappears, making it clear that the verification approach needs to be updated.
| if (spField is not null) | |
| { | |
| var innerProvider = spField.GetValue(funcClient) as IServiceProvider; | |
| Assert.NotNull(innerProvider); | |
| var marker = innerProvider!.GetService<IMarkerService>(); | |
| Assert.NotNull(marker); | |
| } | |
| Assert.NotNull(spField); | |
| var innerProvider = spField!.GetValue(funcClient) as IServiceProvider; | |
| Assert.NotNull(innerProvider); | |
| var marker = innerProvider!.GetService<IMarkerService>(); | |
| Assert.NotNull(marker); |
All four AddAIAgent overloads in AgentHostingServiceCollectionExtensions were creating ChatClientAgent without forwarding the IServiceProvider. This meant tools registered via dependency injection could not resolve their dependencies at invocation time. Pass services: sp to each ChatClientAgent constructor call so that the FunctionInvokingChatClient middleware receives the application's service provider and can resolve tool dependencies correctly. Fixes microsoft#4453 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
8906f26 to
a3d7b31
Compare
Motivation and Context
All four AddAIAgent overloads in AgentHostingServiceCollectionExtensions were creating ChatClientAgent without forwarding the IServiceProvider. This meant tools registered via dependency injection could not resolve their dependencies at invocation time.
Description
Pass services: sp to each ChatClientAgent constructor call so that the FunctionInvokingChatClient middleware receives the application's service provider and can resolve tool dependencies correctly.
Fixes #4453
Contribution Checklist