// 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)
{
}
}