// Copyright (c) Microsoft. All rights reserved. using System.Text; using System.Text.Json; using Microsoft.Extensions.AI; using Microsoft.Extensions.AI.Agents; using Microsoft.Shared.Samples; using OpenAIClient = OpenAI.OpenAIClient; namespace Microsoft.Shared.SampleUtilities; /// /// Provides a base class for orchestration samples that demonstrates agent orchestration scenarios. /// Inherits from and provides utility methods for creating agents, chat clients, /// and writing responses to the console or test output. /// public abstract class OrchestrationSample : BaseSample { /// /// Creates a new instance using the specified instructions, description, name, and functions. /// /// The instructions to provide to the agent. /// An optional description for the agent. /// An optional name for the agent. /// A set of instances to be used as tools by the agent. /// A new instance configured with the provided parameters. protected ChatClientAgent CreateAgent(string instructions, string? description = null, string? name = null, params AIFunction[] functions) { // Get the chat client to use for the agent. using IChatClient chatClient = CreateChatClient(); ChatClientAgentOptions options = new() { Name = name, Description = description, Instructions = instructions, ChatOptions = new() { Tools = functions, ToolMode = ChatToolMode.Auto } }; return new ChatClientAgent(chatClient, options); } /// /// Creates and configures a new instance using the OpenAI client and test configuration. /// /// A configured instance ready for use with agents. protected IChatClient CreateChatClient() { return new OpenAIClient(TestConfiguration.OpenAI.ApiKey) .GetChatClient(TestConfiguration.OpenAI.ChatModelId) .AsIChatClient() .AsBuilder() .UseFunctionInvocation() .Build(); } /// /// Display the provided history. /// /// The history to display protected void DisplayHistory(IEnumerable history) { Console.WriteLine("\n\nORCHESTRATION HISTORY"); foreach (ChatMessage message in history) { this.WriteMessageOutput(message); } } /// /// Writes the provided messages to the console or test output, including role and author information. /// /// An enumerable of objects to write. protected static void WriteResponse(IEnumerable response) { foreach (ChatMessage message in response) { if (!string.IsNullOrEmpty(message.Text)) { System.Console.WriteLine($"\n# RESPONSE {message.Role}{(message.AuthorName is not null ? $" - {message.AuthorName}" : string.Empty)}: {message}"); } } } /// /// Writes the streamed agent run response updates to the console or test output, including role and author information. /// /// An enumerable of objects representing streamed responses. protected static void WriteStreamedResponse(IEnumerable streamedResponses) { string? authorName = null; ChatRole? authorRole = null; StringBuilder builder = new(); foreach (AgentRunResponseUpdate response in streamedResponses) { authorName ??= response.AuthorName; authorRole ??= response.Role; if (!string.IsNullOrEmpty(response.Text)) { builder.Append($"({JsonSerializer.Serialize(response.Text)})"); } } if (builder.Length > 0) { System.Console.WriteLine($"\n# STREAMED {authorRole ?? ChatRole.Assistant}{(authorName is not null ? $" - {authorName}" : string.Empty)}: {builder}\n"); } } /// /// Provides monitoring and callback functionality for orchestration scenarios, including tracking streamed responses and message history. /// protected sealed class OrchestrationMonitor { /// /// Gets the list of streamed response updates received so far. /// public List StreamedResponses { get; } = []; /// /// Gets the list of chat messages representing the conversation history. /// public List History { get; } = []; /// /// Callback to handle a batch of chat messages, adding them to history and writing them to output. /// /// The collection of objects to process. /// A representing the asynchronous operation. public ValueTask ResponseCallback(IEnumerable response) { WriteStreamedResponse(this.StreamedResponses); this.StreamedResponses.Clear(); this.History.AddRange(response); WriteResponse(response); return default; } /// /// Callback to handle a streamed agent run response update, adding it to the list and writing output if final. /// /// The to process. /// A representing the asynchronous operation. public ValueTask StreamingResultCallback(AgentRunResponseUpdate streamedResponse) { this.StreamedResponses.Add(streamedResponse); return default; } } /// /// Initializes a new instance of the class, setting up logging, configuration, and /// optionally redirecting output to the test output. /// /// This constructor initializes logging using an and sets up /// configuration from multiple sources, including a JSON file, environment variables, and user secrets. /// If is , calls to /// will be redirected to the test output provided by . /// /// The instance used to write test output. /// /// A value indicating whether output should be redirected to the test output. to redirect; otherwise, . /// protected OrchestrationSample(ITestOutputHelper output, bool redirectSystemConsoleOutput = true) : base(output, redirectSystemConsoleOutput) { } }