// Copyright (c) Microsoft. All rights reserved. using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.Chat; using Microsoft.SemanticKernel.Agents.OpenAI; using Microsoft.SemanticKernel.ChatCompletion; using OpenAI.Assistants; namespace Agents; /// /// Demonstrate the serialization of with a /// and an . /// public class MixedChat_Serialization(ITestOutputHelper output) : BaseAssistantTest(output) { private const string TranslatorName = "Translator"; private const string TranslatorInstructions = """ Spell the last number in chat as a word in english and spanish on a single line without any line breaks. """; private const string CounterName = "Counter"; private const string CounterInstructions = """ Increment the last number from your most recent response. Never repeat the same number. Only respond with a single number that is the result of your calculation without explanation. """; [Theory] [InlineData(true)] [InlineData(false)] public async Task SerializeAndRestoreAgentGroupChat(bool useChatClient) { // Define the agents: one of each type ChatCompletionAgent agentTranslator = new() { Instructions = TranslatorInstructions, Name = TranslatorName, Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient), }; // Define the assistant Assistant assistant = await this.AssistantClient.CreateAssistantAsync( this.Model, name: CounterName, instructions: CounterInstructions, metadata: SampleMetadata); // Create the agent OpenAIAssistantAgent agentCounter = new(assistant, this.AssistantClient); AgentGroupChat chat = CreateGroupChat(); // Invoke chat and display messages. ChatMessageContent input = new(AuthorRole.User, "1"); chat.AddChatMessage(input); this.WriteAgentChatMessage(input); Console.WriteLine("============= Dynamic Agent Chat - Primary (prior to serialization) =============="); await InvokeAgents(chat); AgentGroupChat copy = CreateGroupChat(); Console.WriteLine("\n=========== Serialize and restore the Agent Chat into a new instance ============"); await CloneChatAsync(chat, copy); Console.WriteLine("\n============ Continue with the dynamic Agent Chat (after deserialization) ==============="); await InvokeAgents(copy); Console.WriteLine("\n============ The entire Agent Chat (includes messages prior to serialization and those after deserialization) =============="); await foreach (ChatMessageContent content in copy.GetChatMessagesAsync()) { this.WriteAgentChatMessage(content); } chatClient?.Dispose(); async Task InvokeAgents(AgentGroupChat chat) { await foreach (ChatMessageContent content in chat.InvokeAsync()) { this.WriteAgentChatMessage(content); } } async Task CloneChatAsync(AgentGroupChat source, AgentGroupChat clone) { await using MemoryStream stream = new(); await AgentChatSerializer.SerializeAsync(source, stream); stream.Position = 0; using StreamReader reader = new(stream); Console.WriteLine(await reader.ReadToEndAsync()); stream.Position = 0; AgentChatSerializer serializer = await AgentChatSerializer.DeserializeAsync(stream); await serializer.DeserializeAsync(clone); } AgentGroupChat CreateGroupChat() => new(agentTranslator, agentCounter) { ExecutionSettings = new() { TerminationStrategy = new CountingTerminationStrategy(5) { // Only the art-director may approve. Agents = [agentTranslator], // Limit total number of turns MaximumIterations = 20, } } }; } private sealed class CountingTerminationStrategy(int maxTurns) : TerminationStrategy { private int _count = 0; protected override Task ShouldAgentTerminateAsync(Agent agent, IReadOnlyList history, CancellationToken cancellationToken) { ++this._count; bool shouldTerminate = this._count >= maxTurns; return Task.FromResult(shouldTerminate); } } }