// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.ChatCompletion; namespace ChatWithAgent.ApiService; /// /// Controller for agent completions. /// [ApiController] [Route("agent/completions")] public sealed class AgentCompletionsController : ControllerBase { private readonly ChatCompletionAgent _agent; private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// /// The agent. /// The logger. public AgentCompletionsController(ChatCompletionAgent agent, ILogger logger) { this._agent = agent; this._logger = logger; } /// /// Completes the agent request. /// /// The request. /// The cancellation token. [HttpPost] public async Task CompleteAsync([FromBody] AgentCompletionRequest request, CancellationToken cancellationToken) { ValidateChatHistory(request.ChatHistory); // Add the "question" argument used in the agent template. var arguments = new KernelArguments { ["question"] = request.Prompt }; request.ChatHistory.AddUserMessage(request.Prompt); if (request.IsStreaming) { return this.Ok(this.CompleteSteamingAsync(request.ChatHistory, arguments, cancellationToken)); } return this.Ok(this.CompleteAsync(request.ChatHistory, arguments, cancellationToken)); } /// /// Completes the agent request. /// /// The chat history. /// The kernel arguments. /// The cancellation token. /// The completion result. private async IAsyncEnumerable CompleteAsync(ChatHistory chatHistory, KernelArguments arguments, [EnumeratorCancellation] CancellationToken cancellationToken) { var thread = new ChatHistoryAgentThread(chatHistory); IAsyncEnumerable> content = this._agent.InvokeAsync(thread, options: new() { KernelArguments = arguments }, cancellationToken: cancellationToken); await foreach (ChatMessageContent item in content.ConfigureAwait(false)) { yield return item; } } /// /// Completes the agent request with streaming. /// /// The chat history. /// The kernel arguments. /// The cancellation token. /// The completion result. private async IAsyncEnumerable CompleteSteamingAsync(ChatHistory chatHistory, KernelArguments arguments, [EnumeratorCancellation] CancellationToken cancellationToken) { var thread = new ChatHistoryAgentThread(chatHistory); IAsyncEnumerable> content = this._agent.InvokeStreamingAsync(thread, options: new() { KernelArguments = arguments }, cancellationToken: cancellationToken); await foreach (StreamingChatMessageContent item in content.ConfigureAwait(false)) { yield return item; } } /// /// Validates the chat history. /// /// The chat history to validate. private static void ValidateChatHistory(ChatHistory chatHistory) { foreach (ChatMessageContent content in chatHistory) { if (content.Role == AuthorRole.System) { throw new ArgumentException("A system message is provided by the agent and should not be included in the chat history."); } } } }