-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
.Net Agents - ChatHistory Reducer Pattern (#7570)
### Motivation and Context <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> Introduce ability to reduce the length of the chat-history when using the _Agent Framework_. A prolonged agent dialog can exceed the allowed token limit. As such, introducing a technique for managing chat-history is critical. Such a technique must be available for both `AgentChat` and _No-Chat_ invocation modes. > Note: This consideration only applies to `ChatCompletionAgent` as the Assistant API manages this internally for `OpenAIAssistantAgent`. ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> - Define `IChatHistoryReducer` contract - Add `ChatCompletionAgent.HistoryReducer` nullable/optional property - Update `ChatCompletionAgent.GetChannelKeys()` to assign agents to dedicted channels based on reducer. - Implement "Top N" truncating reducer: `ChatHistoryTruncationReducer` - Implement summarizing reducer: `ChatHistorySummarizationReducer` - Allow prompt customization for `ChatHistorySummarizationReducer` - Update `ChatHistoryChannel` to make use of agent-reducer, if present - Add `ChatCompletion_HistoryReducer` sample under _Concepts_ ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [x] The code builds clean without any errors or warnings - [X] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [X] All unit tests pass, and I have added new tests where possible - [X] I didn't break anyone 😄
- Loading branch information
Showing
20 changed files
with
1,363 additions
and
125 deletions.
There are no files selected for viewing
173 changes: 173 additions & 0 deletions
173
dotnet/samples/Concepts/Agents/ChatCompletion_HistoryReducer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
using Microsoft.SemanticKernel; | ||
using Microsoft.SemanticKernel.Agents; | ||
using Microsoft.SemanticKernel.Agents.History; | ||
using Microsoft.SemanticKernel.ChatCompletion; | ||
|
||
namespace Agents; | ||
|
||
/// <summary> | ||
/// Demonstrate creation of <see cref="ChatCompletionAgent"/> and | ||
/// eliciting its response to three explicit user messages. | ||
/// </summary> | ||
public class ChatCompletion_HistoryReducer(ITestOutputHelper output) : BaseTest(output) | ||
{ | ||
private const string TranslatorName = "NumeroTranslator"; | ||
private const string TranslatorInstructions = "Add one to latest user number and spell it in spanish without explanation."; | ||
|
||
/// <summary> | ||
/// Demonstrate the use of <see cref="ChatHistoryTruncationReducer"/> when directly | ||
/// invoking a <see cref="ChatCompletionAgent"/>. | ||
/// </summary> | ||
[Fact] | ||
public async Task TruncatedAgentReductionAsync() | ||
{ | ||
// Define the agent | ||
ChatCompletionAgent agent = CreateTruncatingAgent(10, 10); | ||
|
||
await InvokeAgentAsync(agent, 50); | ||
} | ||
|
||
/// <summary> | ||
/// Demonstrate the use of <see cref="ChatHistorySummarizationReducer"/> when directly | ||
/// invoking a <see cref="ChatCompletionAgent"/>. | ||
/// </summary> | ||
[Fact] | ||
public async Task SummarizedAgentReductionAsync() | ||
{ | ||
// Define the agent | ||
ChatCompletionAgent agent = CreateSummarizingAgent(10, 10); | ||
|
||
await InvokeAgentAsync(agent, 50); | ||
} | ||
|
||
/// <summary> | ||
/// Demonstrate the use of <see cref="ChatHistoryTruncationReducer"/> when using | ||
/// <see cref="AgentGroupChat"/> to invoke a <see cref="ChatCompletionAgent"/>. | ||
/// </summary> | ||
[Fact] | ||
public async Task TruncatedChatReductionAsync() | ||
{ | ||
// Define the agent | ||
ChatCompletionAgent agent = CreateTruncatingAgent(10, 10); | ||
|
||
await InvokeChatAsync(agent, 50); | ||
} | ||
|
||
/// <summary> | ||
/// Demonstrate the use of <see cref="ChatHistorySummarizationReducer"/> when using | ||
/// <see cref="AgentGroupChat"/> to invoke a <see cref="ChatCompletionAgent"/>. | ||
/// </summary> | ||
[Fact] | ||
public async Task SummarizedChatReductionAsync() | ||
{ | ||
// Define the agent | ||
ChatCompletionAgent agent = CreateSummarizingAgent(10, 10); | ||
|
||
await InvokeChatAsync(agent, 50); | ||
} | ||
|
||
// Proceed with dialog by directly invoking the agent and explicitly managing the history. | ||
private async Task InvokeAgentAsync(ChatCompletionAgent agent, int messageCount) | ||
{ | ||
ChatHistory chat = []; | ||
|
||
int index = 1; | ||
while (index <= messageCount) | ||
{ | ||
// Provide user input | ||
chat.Add(new ChatMessageContent(AuthorRole.User, $"{index}")); | ||
Console.WriteLine($"# {AuthorRole.User}: '{index}'"); | ||
|
||
// Reduce prior to invoking the agent | ||
bool isReduced = await agent.ReduceAsync(chat); | ||
|
||
// Invoke and display assistant response | ||
await foreach (ChatMessageContent message in agent.InvokeAsync(chat)) | ||
{ | ||
chat.Add(message); | ||
Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}: '{message.Content}'"); | ||
} | ||
|
||
index += 2; | ||
|
||
// Display the message count of the chat-history for visibility into reduction | ||
Console.WriteLine($"@ Message Count: {chat.Count}\n"); | ||
|
||
// Display summary messages (if present) if reduction has occurred | ||
if (isReduced) | ||
{ | ||
int summaryIndex = 0; | ||
while (chat[summaryIndex].Metadata?.ContainsKey(ChatHistorySummarizationReducer.SummaryMetadataKey) ?? false) | ||
{ | ||
Console.WriteLine($"\tSummary: {chat[summaryIndex].Content}"); | ||
++summaryIndex; | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Proceed with dialog with AgentGroupChat. | ||
private async Task InvokeChatAsync(ChatCompletionAgent agent, int messageCount) | ||
{ | ||
AgentGroupChat chat = new(); | ||
|
||
int lastHistoryCount = 0; | ||
|
||
int index = 1; | ||
while (index <= messageCount) | ||
{ | ||
// Provide user input | ||
chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, $"{index}")); | ||
Console.WriteLine($"# {AuthorRole.User}: '{index}'"); | ||
|
||
// Invoke and display assistant response | ||
await foreach (ChatMessageContent message in chat.InvokeAsync(agent)) | ||
{ | ||
Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}: '{message.Content}'"); | ||
} | ||
|
||
index += 2; | ||
|
||
// Display the message count of the chat-history for visibility into reduction | ||
// Note: Messages provided in descending order (newest first) | ||
ChatMessageContent[] history = await chat.GetChatMessagesAsync(agent).ToArrayAsync(); | ||
Console.WriteLine($"@ Message Count: {history.Length}\n"); | ||
|
||
// Display summary messages (if present) if reduction has occurred | ||
if (history.Length < lastHistoryCount) | ||
{ | ||
int summaryIndex = history.Length - 1; | ||
while (history[summaryIndex].Metadata?.ContainsKey(ChatHistorySummarizationReducer.SummaryMetadataKey) ?? false) | ||
{ | ||
Console.WriteLine($"\tSummary: {history[summaryIndex].Content}"); | ||
--summaryIndex; | ||
} | ||
} | ||
|
||
lastHistoryCount = history.Length; | ||
} | ||
} | ||
|
||
private ChatCompletionAgent CreateSummarizingAgent(int reducerMessageCount, int reducerThresholdCount) | ||
{ | ||
Kernel kernel = this.CreateKernelWithChatCompletion(); | ||
return | ||
new() | ||
{ | ||
Name = TranslatorName, | ||
Instructions = TranslatorInstructions, | ||
Kernel = kernel, | ||
HistoryReducer = new ChatHistorySummarizationReducer(kernel.GetRequiredService<IChatCompletionService>(), reducerMessageCount, reducerThresholdCount), | ||
}; | ||
} | ||
|
||
private ChatCompletionAgent CreateTruncatingAgent(int reducerMessageCount, int reducerThresholdCount) => | ||
new() | ||
{ | ||
Name = TranslatorName, | ||
Instructions = TranslatorInstructions, | ||
Kernel = this.CreateKernelWithChatCompletion(), | ||
HistoryReducer = new ChatHistoryTruncationReducer(reducerMessageCount, reducerThresholdCount), | ||
}; | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.