.NET: Add AsIChatClient extension to expose any AIAgent as an IChatClient - #7687
.NET: Add AsIChatClient extension to expose any AIAgent as an IChatClient#7687Tomas Rampas (tomas-rampas) wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an adapter allowing any .NET AIAgent to be consumed as an IChatClient.
Changes:
- Adds the
AsIChatClientextension with session and usage guidance. - Implements response conversion, streaming, cancellation, options, metadata, and service forwarding.
- Adds comprehensive unit and integration-style coverage.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
dotnet/src/Microsoft.Agents.AI/AgentExtensions.cs |
Exposes the new public extension method. |
dotnet/src/Microsoft.Agents.AI/ChatClient/AIAgentChatClient.cs |
Implements the agent-to-chat-client adapter. |
dotnet/tests/Microsoft.Agents.AI.UnitTests/AIAgentChatClientTests.cs |
Tests adapter behavior and integration. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
…ient Adds AIAgentExtensions.AsIChatClient(this AIAgent, AgentSession? = null), backed by an internal AIAgentChatClient adapter, so any agent can be used where Microsoft.Extensions.AI.IChatClient is accepted (e.g. as an evaluation judge in Microsoft.Extensions.AI.Evaluation). - Maps GetResponseAsync/GetStreamingResponseAsync to RunAsync/ RunStreamingAsync, reusing the AgentResponse converters; streaming honors WithCancellation via EnumeratorCancellation. - Carries ChatOptions through ChatClientAgentRunOptions; ResponseFormat is also copied to the base AgentRunOptions so structured output works for non-ChatClient agents. - GetService returns the adapter for unkeyed IChatClient requests (preserving the full agent pipeline), forwards everything else to the agent, and synthesizes ChatClientMetadata as a fallback. - Stateless per call by default; optional bound session mirrors AsAIFunction semantics. - 24 unit tests incl. ChatClientAgent end-to-end, structured output, GetService precedence, and cancellation propagation. Addresses microsoft#3496
d009b77 to
23aca4f
Compare
@microsoft-github-policy-service agree |
|
Not all So, given the requirement in the PR, the proper way would be. var anyAiAgent = ...
var chatClient = anyAIAgent.GetService<IChatClient>();
// use the chat client from here. |
|
Thanks Roger Barreto (@rogerbarreto), the compatibility concern is fair - that's also why the remarks document what each agent type honors, and why I asked in the PR description if this should go under But I don't think
If you prefer to keep the surface constrained while the shape settles, I can add |
The repo's own ChatClientExtensions is declared in the Microsoft.Extensions.AI namespace, so the fully-qualified cref never disambiguated anything; CI's dotnet format (SDK 10.0.400) flags it.
Motivation & Context
More and more .NET APIs accept
Microsoft.Extensions.AI.IChatClient. This change lets anyAIAgentbe used wherever anIChatClientis accepted — the motivating scenario from the issue thread is using an agent as the LLM behindMicrosoft.Extensions.AI.Evaluationjudges. Implements the proposal I claimed on #3496 (API shape posted there for early feedback).Description & Review Guide
What are the major changes?
AIAgentExtensions.AsIChatClient(this AIAgent agent, AgentSession? session = null)inMicrosoft.Agents.AI— mirrors the siblingAsAIFunction(..., AgentSession?)and theAsIChatClientnaming already used by the provider-client adapters in this repo.internal sealed class AIAgentChatClient : IChatClient(src/Microsoft.Agents.AI/ChatClient/AIAgentChatClient.cs):GetResponseAsync→agent.RunAsync(...)→ the existingAgentResponse.AsChatResponse()converter (rawChatResponsepass-through preserved, soConversationId/usage survive forChatClientAgent).GetStreamingResponseAsyncvalidates eagerly (throws before enumeration), then streams via a private[EnumeratorCancellation]iterator using the singularAsChatResponseUpdate()converter, soWithCancellation(...)tokens are honored.ChatOptionsare carried throughChatClientAgentRunOptions(honored byChatClientAgent, gracefully ignored by agents that don't understand them);ResponseFormatis additionally copied onto the baseAgentRunOptionsso structured output (GetResponseAsync<T>) works for every agent type.GetService: unkeyedIChatClientrequests return the adapter (preserving the full agent pipeline — instructions, tools, context providers); everything else forwards to the agent;ChatClientMetadatais synthesized as a last-resort fallback.Disposeis a no-op; the caller owns the agent lifetime.tests/Microsoft.Agents.AI.UnitTests/AIAgentChatClientTests.cs), includingChatClientAgentend-to-end (instructions/tools merge,ConversationIdround-trip), M.E.AI structured-output through the adapter,GetServiceprecedence pinned against a realChatClientAgent, and cancellation propagation on both paths (mutation-tested: breaking token forwarding fails 4 tests).What is the impact of these changes? Purely additive: one new public method, no modified lines in existing code. Release build passes Package Validation with zero CP diagnostics. Default usage is stateless per call (full history each request); an optional bound session enables stateful use with documented caveats (no concurrent use, don't share across users).
What do you want reviewers to focus on?
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]?Microsoft.Agents.AIis GA-validated (baseline 1.0.0), and the adapter has documented sharp edges (background/continuation responses unsupported; non-ChatClientAgentagents ignore mostChatOptions). The closest structural analogue (ChatStrategyExtensions.AsChatReducer) is gated; the siblingAsAIFunctionis not. Happy to add the attribute if you prefer to let the shape settle — please advise.ChatOptions.ContinuationTokenis passed through rather than rejected up front; raw tokens don't round-trip forChatClientAgent(its token validation fails loudly). This is a deliberate choice — documented as unsupported in the remarks — so a future agent that accepts raw tokens isn't blocked. Can switch to fail-fast if preferred.ChatOptionspass-through means callers can add tools / append instructions for agents honoringChatClientAgentRunOptions— same capability the agent holder already has viaRunAsync; the remarks point untrusted-caller scenarios at theRejectRequestSettings/RunOptionsFactorypattern fromMicrosoft.Agents.AI.Hosting.OpenAI.Offered as follow-ups (kept out to keep this PR small): a sample mirroring
Agent_Step09_AsFunctionToolshowing an agent as an M.E.AI.Evaluation judge; additional tests (cancelled-token →OperationCanceledExceptionend-to-end, exception propagation unwrapped, callerChatOptionsnon-mutation regression).Related Issue
Fixes #3496
No other open PR exists for this issue.
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.