Add extensible chat client routing - #7662
Conversation
Introduce one-shot and failover routing primitives, ordered and semantic routing implementations, lifecycle reporting, streaming safeguards, and focused routing tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 74d04840-2379-4615-93f7-84f2299ada74
This comment was marked as resolved.
This comment was marked as resolved.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Treat provider-thrown cancellation as terminal, materialize semantic routing messages before inspection, and enforce non-null selection consistently for streaming. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 74d04840-2379-4615-93f7-84f2299ada74
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 74d04840-2379-4615-93f7-84f2299ada74
Capture Current access failures before committing output so pre-output failover and attempt reporting remain accurate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 74d04840-2379-4615-93f7-84f2299ada74
Apply ConfigureAwait(false) consistently across selection, invocation, streaming, disposal, and routing update awaits. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 74d04840-2379-4615-93f7-84f2299ada74
There was a problem hiding this comment.
Addressed in commit 0abbfd1, along with some cancellation races
a19b74b to
0abbfd1
Compare
Clarify RoutingContext input semantics, add optional message buffering, centralize validation, simplify timing and disposal, and separate caller cancellation from provider failures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 74d04840-2379-4615-93f7-84f2299ada74
Clone request options for selector shaping and restrict routing updates to concrete client attempts, leaving selection-failure cleanup to selectors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 74d04840-2379-4615-93f7-84f2299ada74
Follow MEAI's repeatable-enumerable convention and propagate selector failures without cancellation rewriting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 74d04840-2379-4615-93f7-84f2299ada74
Use ConcurrentDictionary atomic operations instead of explicit lock blocks while preserving the narrow request-state lifetime. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 74d04840-2379-4615-93f7-84f2299ada74
896e51d to
a576773
Compare
Keep client selection simple while preventing selector and failed-client option mutations from affecting downstream attempts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 74d04840-2379-4615-93f7-84f2299ada74
Document client-reference identity and keep semantic scoring parameters together in the constructor signature. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 74d04840-2379-4615-93f7-84f2299ada74
Align routing code with MEAI formatting and documentation conventions, simplify disposal, and split routing tests by component and assembly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 74d04840-2379-4615-93f7-84f2299ada74
|
Thank you David for the feedback, addressed in 8b79ae4 |
Consolidate shared streaming and non-streaming cases, reuse routing test helpers, and simplify semantic router disposal. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 74d04840-2379-4615-93f7-84f2299ada74
|
Sorry y'all, I had a change of thought -- just one last thing I felt was a bit unresolved: I actually think the small per-attempt selection result mentioned earlier is more ergonomic than requiring every option variation to be represented by a configured client:
@jozkee @PranavSenthilnathan let me know what you guys think when you have time. I kicked off a Copilot session to write it out and push it here -- if you guys prefer the old way then I’ll revert it. I do really like the simplicity of returning a singular IChatClient -- the contract is a lot cleaner -- and I understand if this new approach 'muddies the waters', or so to speak. |
|
@joshuajyue I don’t believe selection-specific options are a first-class scenario. Neither It may be a scenario we should support, but it does not necessarily need to be part of the initial API. I also think it would be better to align with protected RoutingChatClient(
Action<RoutingContext, IChatClient, ChatOptions> configureOptions);
public static RoutingChatClient Create(
Func<RoutingContext, CancellationToken, ValueTask<IChatClient>> selector,
Action<RoutingContext, IChatClient, ChatOptions> configureOptions); |
|
Reverting to the prev architecture. I found some clean up in ordered failover client, so that'll be what the commit is -- but it's still on top of the pre-change-of-thought commit. |
Allow repeated client instances in ordered failover. Selection now reads a stored index instead of reverse-resolving the attempted client by reference, so the constructor no longer requires unique instances and a client may appear more than once, being invoked once per position. The per-request state holds only the next index. Exhaustion is detected when the index advances past the last client, so the final failure is rethrown from the routing update rather than stored and rethrown from the next selection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 74d04840-2379-4615-93f7-84f2299ada74
626a79d to
2a2e284
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/Libraries/Microsoft.Extensions.AI/ChatRouting/FailoverChatClient.cs:292
- In the streaming path, disposing/abandoning the outer async enumeration before any update is exposed (and without cancellation/exception) leaves
responseCompletedasfalseandterminalExceptionasnull, which currently results inisTerminalAttemptbeing reported asfalse. That implies another selection will follow, but disposal means routing is actually terminal and policy implementations may leak per-request state (e.g., ordered failover stores the next index on nonterminal updates). Consider treating this "no exception + not completed" condition as terminal.
isTerminalAttempt =
attempt.ResponseCompleted ||
outputCommitted ||
cancellationRequested ||
reachedAttemptLimit;
src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatRouting/RoutingContext.cs:49
- PR metadata/issue proposal describe
RoutingContextas exposing mutable request messages/options (settable properties). In this implementation both properties are get-only (andChatOptionsis a cloned snapshot), which makes it impossible for routing/failover policies to replace the message sequence or swap options between attempts via the context. Either update the published API shape/docs to match (and adjust API baselines/tests accordingly), or revise the PR description to reflect that only the contents of the returnedChatOptions(and any mutable underlying message collection) can be changed.
/// <summary>Gets the messages supplied to client selection and the selected client.</summary>
/// <remarks>
/// Selection and failover may enumerate this sequence multiple times. Callers should supply a repeatable sequence.
/// </remarks>
public IEnumerable<ChatMessage> Messages { get; }
/// <summary>Gets a snapshot of the request options supplied to client selection.</summary>
/// <remarks>
/// Changes do not affect the caller's instance or the options passed to the selected client. Client-specific
/// behavior should generally be attached to the returned client.
/// </remarks>
public ChatOptions? ChatOptions { get; }
Document RoutingChatClient as an experimental capability in docs/ai/ichatclient.md, mirroring the existing 'Chat reduction (experimental)' section: an IMPORTANT experimental note plus a concise description with xref links to RoutingChatClient, SemanticRoutingChatClient, and OrderedFailoverChatClient, matching the API shape merged in dotnet/extensions#7662. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7f6512d2-cfbc-4df3-8401-acc729c949e6
Document RoutingChatClient and FailoverChatClient as experimental capabilities in docs/ai/ichatclient.md, mirroring the existing 'Chat reduction (experimental)' section: an IMPORTANT experimental note plus a concise description with xref links to RoutingChatClient, FailoverChatClient, OrderedFailoverChatClient, and SemanticRoutingChatClient, matching the API shape merged in dotnet/extensions#7662. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7f6512d2-cfbc-4df3-8401-acc729c949e6
Resolves #7647
Summary
Adds experimental chat-routing primitives for selecting configured
IChatClientinstances without imposing a particular routing policy.RoutingChatClientsupports one-shot client selection.FailoverChatClientretries uncanceled failures that occur before output is exposed.OrderedFailoverChatClientprovides ordered fallback.SemanticRoutingChatClientselects clients using cached profile embeddings.RoutingContextexposes mutable request messages and options.FailoverChatClientAttemptreports invocation outcome, duration, time to first update, completion, and output commitment.API shape
Routing policies have two seams:
SelectClientAsyncruns before every invocation.OnRoutingUpdateAsyncreports each attempt so policy state can influence the next selection.isTerminalmeans the base will not select another client after the callback completes. A null attempt represents selection terminating before a client was invoked.Failover behavior
Semantic routing
SemanticRoutingChatClient:topK: 1.leaveOpen.Tests
Coverage includes selection failures, retries, cancellation, attempt limits, streaming commitment, early disposal, enumerator failures, concurrent requests, state cleanup, semantic thresholds, top-K aggregation, caching, and ownership.
net10.0net472test project builds successfullyMicrosoft Reviewers: Open in CodeFlow