.NET: Allow Simulating service stored ChatHistory to improve consistency (#4974)

* Allow Simulating service stored ChatHistory to improve consistency

* Fixing bug in ServiceStoredSimulatingChatClient

* Addressing PR comments.

* Address PR comments

* Apply suggestion from @SergeyMenshykh

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Fix bug

---------

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
This commit is contained in:
westey
2026-03-30 18:52:01 +00:00
committed by GitHub
co-authored by SergeyMenshykh
parent 18f7ba8632
commit 401e5dc7e8
16 changed files with 950 additions and 714 deletions
@@ -63,14 +63,17 @@ public static class ChatClientExtensions
});
}
// ChatHistoryPersistingChatClient is registered after FunctionInvokingChatClient so that it sits
// between FIC and the leaf client. ChatClientBuilder.Build applies factories in reverse order,
// making the first Use() call outermost. By adding our decorator second, the resulting pipeline is:
// FunctionInvokingChatClient → ChatHistoryPersistingChatClient → leaf IChatClient
// This allows the decorator to persist messages after each individual service call within
// FIC's function invocation loop, or to mark them for later persistence at the end of the run.
bool markOnly = options?.PersistChatHistoryAtEndOfRun is true;
chatBuilder.Use(innerClient => new ChatHistoryPersistingChatClient(innerClient, markOnly));
// ServiceStoredSimulatingChatClient is only injected when SimulateServiceStoredChatHistory is enabled.
// It is registered after FunctionInvokingChatClient so that it sits between FIC and the leaf client.
// ChatClientBuilder.Build applies factories in reverse order, making the first Use() call outermost.
// By adding our decorator second, the resulting pipeline is:
// FunctionInvokingChatClient → ServiceStoredSimulatingChatClient → leaf IChatClient
// This allows the decorator to simulate service-stored chat history by loading history before
// each service call, persisting after each call, and returning a sentinel ConversationId.
if (options?.SimulateServiceStoredChatHistory is true)
{
chatBuilder.Use(innerClient => new ServiceStoredSimulatingChatClient(innerClient));
}
var agentChatClient = chatBuilder.Build(services);