Partilhar via


Como fazer: Coordenar a colaboração do agente usando o bate-papo de grupo do agente (experimental)

Aviso

O Semantic Kernel Agent Framework é experimental, ainda está em desenvolvimento e está sujeito a alterações.

Descrição geral

Neste exemplo, exploraremos como usar o Chat de Grupo de Agentes para coordenar o agrupamento de dois agentes diferentes que trabalham para revisar e reescrever o conteúdo fornecido pelo usuário. A cada agente é atribuída uma função distinta:

  • Revisor: Revisa e fornece direção ao Writer.
  • Writer: Atualiza o conteúdo do usuário com base na entrada do Revisor .

A abordagem será dividida passo a passo para destacar as principais partes do processo de codificação.

Introdução

Antes de prosseguir com a codificação de recursos, verifique se o ambiente de desenvolvimento está totalmente configurado e configurado.

Comece criando um projeto de console . Em seguida, inclua as seguintes referências de pacote para garantir que todas as dependências necessárias estejam disponíveis.

Para adicionar dependências de pacote a partir da linha de comando, use o dotnet comando:

dotnet add package Azure.Identity
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Binder
dotnet add package Microsoft.Extensions.Configuration.UserSecrets
dotnet add package Microsoft.Extensions.Configuration.EnvironmentVariables
dotnet add package Microsoft.SemanticKernel.Connectors.AzureOpenAI
dotnet add package Microsoft.SemanticKernel.Agents.Core --prerelease

Se estiver gerenciando pacotes NuGet no Visual Studio, verifique se Include prerelease está marcado.

O ficheiro de projeto (.csproj) deve conter as seguintes PackageReference definições:

  <ItemGroup>
    <PackageReference Include="Azure.Identity" Version="<stable>" />
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="<stable>" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="<stable>" />
    <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="<stable>" />
    <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="<stable>" />
    <PackageReference Include="Microsoft.SemanticKernel.Agents.Core" Version="<latest>" />
    <PackageReference Include="Microsoft.SemanticKernel.Connectors.AzureOpenAI" Version="<latest>" />
  </ItemGroup>

O Agent Framework é experimental e requer supressão de aviso. Isso pode ser abordado como uma propriedade no arquivo de projeto (.csproj):

  <PropertyGroup>
    <NoWarn>$(NoWarn);CA2007;IDE1006;SKEXP0001;SKEXP0110;OPENAI001</NoWarn>
  </PropertyGroup>
import asyncio
import os
import copy
import pyperclip # Install via pip

from semantic_kernel.agents import AgentGroupChat, ChatCompletionAgent
from semantic_kernel.agents.strategies.selection.kernel_function_selection_strategy import (
    KernelFunctionSelectionStrategy,
)
from semantic_kernel.agents.strategies.termination.kernel_function_termination_strategy import (
    KernelFunctionTerminationStrategy,
)
from semantic_kernel.connectors.ai.open_ai.services.azure_chat_completion import AzureChatCompletion
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.functions.kernel_function_decorator import kernel_function
from semantic_kernel.functions.kernel_function_from_prompt import KernelFunctionFromPrompt
from semantic_kernel.kernel import Kernel

Os agentes estão atualmente indisponíveis em Java.

Configuração

Este exemplo requer definição de configuração para se conectar a serviços remotos. Você precisará definir configurações para Open AI ou Azure Open AI.

# Open AI
dotnet user-secrets set "OpenAISettings:ApiKey" "<api-key>"
dotnet user-secrets set "OpenAISettings:ChatModel" "gpt-4o"

# Azure Open AI
dotnet user-secrets set "AzureOpenAISettings:ApiKey" "<api-key>" # Not required if using token-credential
dotnet user-secrets set "AzureOpenAISettings:Endpoint" "<model-endpoint>"
dotnet user-secrets set "AzureOpenAISettings:ChatModelDeployment" "gpt-4o"

A classe a seguir é usada em todos os exemplos de agente. Certifique-se de incluí-lo em seu projeto para garantir a funcionalidade adequada. Esta classe serve como um componente fundamental para os exemplos que se seguem.

using System.Reflection;
using Microsoft.Extensions.Configuration;

namespace AgentsSample;

public class Settings
{
    private readonly IConfigurationRoot configRoot;

    private AzureOpenAISettings azureOpenAI;
    private OpenAISettings openAI;

    public AzureOpenAISettings AzureOpenAI => this.azureOpenAI ??= this.GetSettings<Settings.AzureOpenAISettings>();
    public OpenAISettings OpenAI => this.openAI ??= this.GetSettings<Settings.OpenAISettings>();

    public class OpenAISettings
    {
        public string ChatModel { get; set; } = string.Empty;
        public string ApiKey { get; set; } = string.Empty;
    }

    public class AzureOpenAISettings
    {
        public string ChatModelDeployment { get; set; } = string.Empty;
        public string Endpoint { get; set; } = string.Empty;
        public string ApiKey { get; set; } = string.Empty;
    }

    public TSettings GetSettings<TSettings>() =>
        this.configRoot.GetRequiredSection(typeof(TSettings).Name).Get<TSettings>()!;

    public Settings()
    {
        this.configRoot =
            new ConfigurationBuilder()
                .AddEnvironmentVariables()
                .AddUserSecrets(Assembly.GetExecutingAssembly(), optional: true)
                .Build();
    }
}

A maneira mais rápida de começar com a configuração adequada para executar o código de exemplo é criar um .env arquivo na raiz do seu projeto (onde o script é executado).

Configure as seguintes configurações em seu .env arquivo para o Azure OpenAI ou OpenAI:

AZURE_OPENAI_API_KEY="..."
AZURE_OPENAI_ENDPOINT="https://..."
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME="..."
AZURE_OPENAI_API_VERSION="..."

OPENAI_API_KEY="sk-..."
OPENAI_ORG_ID=""
OPENAI_CHAT_MODEL_ID=""

Uma vez configuradas, as respetivas classes de serviço de IA pegarão as variáveis necessárias e as usarão durante a instanciação.

Os agentes estão atualmente indisponíveis em Java.

Codificação

O processo de codificação para este exemplo envolve:

  1. Configuração - Inicializando as configurações e o plug-in.
  2. Definição do Agente - Crie as duas instâncias do Agente de Conclusão de Chat (Revisor e Gravador).
  3. Definição de Chat - Crie o Chat de Grupo de Agentes e estratégias associadas.
  4. The Chat Loop - Escreva o loop que impulsiona a interação usuário/agente.

O código de exemplo completo é fornecido na seção Final . Consulte essa seção para obter a implementação completa.

Configurar

Antes de criar qualquer Agente de Conclusão de Chat, as definições de configuração, plug-ins e Kernel devem ser inicializados.

Instancie a Settings classe referenciada na seção Configuração anterior.

Settings settings = new();

Os agentes estão atualmente indisponíveis em Java.

Agora, inicialize uma Kernel instância com um IChatCompletionServicearquivo .

IKernelBuilder builder = Kernel.CreateBuilder();

builder.AddAzureOpenAIChatCompletion(
	settings.AzureOpenAI.ChatModelDeployment,
	settings.AzureOpenAI.Endpoint,
	new AzureCliCredential());

Kernel kernel = builder.Build();

Inicialize o objeto do kernel:

kernel = Kernel()

Os agentes estão atualmente indisponíveis em Java.

Vamos também criar uma segunda instância do Kernel via clonagem e adicionar um plug-in que permitirá que o reivew coloque conteúdo atualizado na área de transferência.

Kernel toolKernel = kernel.Clone();
toolKernel.Plugins.AddFromType<ClipboardAccess>();
tool_kernel = copy.deepcopy(kernel)
tool_kernel.add_plugin(ClipboardAccess(), plugin_name="clipboard")

Os agentes estão atualmente indisponíveis em Java.

O plug-in da área de transferência pode ser definido como parte da amostra.

private sealed class ClipboardAccess
{
    [KernelFunction]
    [Description("Copies the provided content to the clipboard.")]
    public static void SetClipboard(string content)
    {
        if (string.IsNullOrWhiteSpace(content))
        {
            return;
        }

        using Process clipProcess = Process.Start(
            new ProcessStartInfo
            {
                FileName = "clip",
                RedirectStandardInput = true,
                UseShellExecute = false,
            });

        clipProcess.StandardInput.Write(content);
        clipProcess.StandardInput.Close();
    }
}

Nota: estamos aproveitando um pacote Python chamado pyperclip. Por favor, instale está usando pip.

class ClipboardAccess:
    @kernel_function
    def set_clipboard(content: str):
        if not content.strip():
            return

        pyperclip.copy(content)

Os agentes estão atualmente indisponíveis em Java.

Definição do agente

Vamos declarar os nomes dos agentes para const que eles possam ser referenciados nas estratégias de Chat de Grupo de Agente :

const string ReviewerName = "Reviewer";
const string WriterName = "Writer";
REVIEWER_NAME = "Reviewer"
WRITER_NAME = "Writer"

Os agentes estão atualmente indisponíveis em Java.

A definição do agente Revisor usa o padrão explorado em How-To: Chat Completion Agent.

Aqui, o revisor recebe a função de responder à entrada do usuário, fornecer orientação ao agente do Writer e verificar o resultado do agente do Writer.

ChatCompletionAgent agentReviewer =
    new()
    {
        Name = ReviewerName,
        Instructions =
            """
            Your responsiblity is to review and identify how to improve user provided content.
            If the user has providing input or direction for content already provided, specify how to address this input.
            Never directly perform the correction or provide example.
            Once the content has been updated in a subsequent response, you will review the content again until satisfactory.
            Always copy satisfactory content to the clipboard using available tools and inform user.

            RULES:
            - Only identify suggestions that are specific and actionable.
            - Verify previous suggestions have been addressed.
            - Never repeat previous suggestions.
            """,
        Kernel = toolKernel,
        Arguments =
            new KernelArguments(
                new AzureOpenAIPromptExecutionSettings() 
                { 
                    FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() 
                })
    };
agent_reviewer = ChatCompletionAgent(
    service_id=REVIEWER_NAME,
    kernel=_create_kernel_with_chat_completion(REVIEWER_NAME),
    name=REVIEWER_NAME,
    instructions="""
        Your responsiblity is to review and identify how to improve user provided content.
        If the user has providing input or direction for content already provided, specify how to 
        address this input.
        Never directly perform the correction or provide example.
        Once the content has been updated in a subsequent response, you will review the content 
        again until satisfactory.
        Always copy satisfactory content to the clipboard using available tools and inform user.

        RULES:
        - Only identify suggestions that are specific and actionable.
        - Verify previous suggestions have been addressed.
        - Never repeat previous suggestions.
        """,
)

Os agentes estão atualmente indisponíveis em Java.

O agente Writer é semelhante, mas não requer a especificação de Configurações de Execução, pois não está configurado com um plug-in.

Aqui o escritor recebe uma tarefa de propósito único, siga a direção e reescreva o conteúdo.

ChatCompletionAgent agentWriter =
    new()
    {
        Name = WriterName,
        Instructions =
            """
            Your sole responsiblity is to rewrite content according to review suggestions.

            - Always apply all review direction.
            - Always revise the content in its entirety without explanation.
            - Never address the user.
            """,
        Kernel = kernel,
    };
agent_writer = ChatCompletionAgent(
    service_id=COPYWRITER_NAME,
    kernel=_create_kernel_with_chat_completion(COPYWRITER_NAME),
    name=COPYWRITER_NAME,
    instructions="""
        Your sole responsiblity is to rewrite content according to review suggestions.

        - Always apply all review direction.
        - Always revise the content in its entirety without explanation.
        - Never address the user.
        """,
)

Os agentes estão atualmente indisponíveis em Java.

Definição de Chat

Definir o Chat de Grupo de Agentes requer considerar as estratégias para selecionar o turno do Agente e determinar quando sair do loop de Chat. Para ambas as considerações, definiremos uma Função de Prompt do Kernel.

O primeiro a raciocinar sobre a seleção do agente :

Usar AgentGroupChat.CreatePromptFunctionForStrategy fornece um mecanismo conveniente para evitar a codificação HTML do paramter de mensagem.

KernelFunction selectionFunction =
    AgentGroupChat.CreatePromptFunctionForStrategy(
        $$$"""
        Examine the provided RESPONSE and choose the next participant.
        State only the name of the chosen participant without explanation.
        Never choose the participant named in the RESPONSE.

        Choose only from these participants:
        - {{{ReviewerName}}}
        - {{{WriterName}}}

        Always follow these rules when choosing the next participant:
        - If RESPONSE is user input, it is {{{ReviewerName}}}'s turn.
        - If RESPONSE is by {{{ReviewerName}}}, it is {{{WriterName}}}'s turn.
        - If RESPONSE is by {{{WriterName}}}, it is {{{ReviewerName}}}'s turn.

        RESPONSE:
        {{$lastmessage}}
        """,
        safeParameterNames: "lastmessage");
selection_function = KernelFunctionFromPrompt(
    function_name="selection",
    prompt=f"""
    Determine which participant takes the next turn in a conversation based on the the most recent participant.
    State only the name of the participant to take the next turn.
    No participant should take more than one turn in a row.

    Choose only from these participants:
    - {REVIEWER_NAME}
    - {COPYWRITER_NAME}

    Always follow these rules when selecting the next participant:
    - After user input, it is {COPYWRITER_NAME}'s turn.
    - After {COPYWRITER_NAME} replies, it is {REVIEWER_NAME}'s turn.
    - After {REVIEWER_NAME} provides feedback, it is {COPYWRITER_NAME}'s turn.

    History:
    {{{{$history}}}}
    """,
)

Os agentes estão atualmente indisponíveis em Java.

O segundo avaliará quando sair do loop de bate-papo:

const string TerminationToken = "yes";

KernelFunction terminationFunction =
    AgentGroupChat.CreatePromptFunctionForStrategy(
        $$$"""
        Examine the RESPONSE and determine whether the content has been deemed satisfactory.
        If content is satisfactory, respond with a single word without explanation: {{{TerminationToken}}}.
        If specific suggestions are being provided, it is not satisfactory.
        If no correction is suggested, it is satisfactory.

        RESPONSE:
        {{$lastmessage}}
        """,
        safeParameterNames: "lastmessage");
TERMINATION_KEYWORD = "yes"

termination_function = KernelFunctionFromPrompt(
    function_name="termination",
    prompt=f"""
        Examine the RESPONSE and determine whether the content has been deemed satisfactory.
        If content is satisfactory, respond with a single word without explanation: {TERMINATION_KEYWORD}.
        If specific suggestions are being provided, it is not satisfactory.
        If no correction is suggested, it is satisfactory.

        RESPONSE:
        {{{{$history}}}}
        """,
)

Os agentes estão atualmente indisponíveis em Java.

Ambas as estratégias exigirão apenas o conhecimento da mensagem de bate-papo mais recente. Isso reduzirá o uso de tokens e ajudará a melhorar o desempenho:

ChatHistoryTruncationReducer historyReducer = new(1);
**ChatHistoryReducer is coming soon to Python.**

Os agentes estão atualmente indisponíveis em Java.

Finalmente, estamos prontos para reunir tudo em nossa definição de Chat de Grupo de Agente .

A criação AgentGroupChat envolve:

  1. Inclua ambos os agentes no construtor.
  2. Defina um KernelFunctionSelectionStrategy usando o previamente definido KernelFunction e Kernel instância.
  3. Defina um KernelFunctionTerminationStrategy usando o previamente definido KernelFunction e Kernel instância.

Observe que cada estratégia é responsável por analisar o KernelFunction resultado.

AgentGroupChat chat =
    new(agentReviewer, agentWriter)
    {
        ExecutionSettings = new AgentGroupChatSettings
        {
            SelectionStrategy =
                new KernelFunctionSelectionStrategy(selectionFunction, kernel)
                {
                    // Always start with the editor agent.
                    InitialAgent = agentReviewer,
                    // Save tokens by only including the final response
                    HistoryReducer = historyReducer,
                    // The prompt variable name for the history argument.
                    HistoryVariableName = "lastmessage",
                    // Returns the entire result value as a string.
                    ResultParser = (result) => result.GetValue<string>() ?? agentReviewer.Name
                },
            TerminationStrategy =
                new KernelFunctionTerminationStrategy(terminationFunction, kernel)
                {
                    // Only evaluate for editor's response
                    Agents = [agentReviewer],
                    // Save tokens by only including the final response
                    HistoryReducer = historyReducer,
                    // The prompt variable name for the history argument.
                    HistoryVariableName = "lastmessage",
                    // Limit total number of turns
                    MaximumIterations = 12,
                    // Customer result parser to determine if the response is "yes"
                    ResultParser = (result) => result.GetValue<string>()?.Contains(TerminationToken, StringComparison.OrdinalIgnoreCase) ?? false
                }
        }
    };

Console.WriteLine("Ready!");

A criação AgentGroupChat envolve:

  1. Inclua ambos os agentes no construtor.
  2. Defina um KernelFunctionSelectionStrategy usando o previamente definido KernelFunction e Kernel instância.
  3. Defina um KernelFunctionTerminationStrategy usando o previamente definido KernelFunction e Kernel instância.

Observe que cada estratégia é responsável por analisar o KernelFunction resultado.

chat = AgentGroupChat(
    agents=[agent_writer, agent_reviewer],
    selection_strategy=KernelFunctionSelectionStrategy(
        function=selection_function,
        kernel=_create_kernel_with_chat_completion("selection"),
        result_parser=lambda result: str(result.value[0]) if result.value is not None else COPYWRITER_NAME,
        agent_variable_name="agents",
        history_variable_name="history",
    ),
    termination_strategy=KernelFunctionTerminationStrategy(
        agents=[agent_reviewer],
        function=termination_function,
        kernel=_create_kernel_with_chat_completion("termination"),
        result_parser=lambda result: TERMINATION_KEYWORD in str(result.value[0]).lower(),
        history_variable_name="history",
        maximum_iterations=10,
    ),
)

Os agentes estão atualmente indisponíveis em Java.

O Loop do Chat

Finalmente, somos capazes de coordenar a interação entre o usuário e o Chat do Grupo de Agente. Comece criando um loop vazio.

Nota: Ao contrário dos outros exemplos, nenhum histórico externo ou thread é gerenciado. O Chat de Grupo do Agente gerencia o histórico de conversas internamente.

bool isComplete = false;
do
{

} while (!isComplete);
is_complete: bool = False
while not is_complete:
    # operational logic

Os agentes estão atualmente indisponíveis em Java.

Agora vamos capturar a entrada do usuário dentro do loop anterior. Neste caso:

  • A entrada vazia será ignorada
  • O termo EXIT sinalizará que a conversa está concluída
  • O termo RESET limpará o histórico do Chat do Grupo de Agentes
  • Qualquer termo que comece com @ será tratado como um caminho de arquivo cujo conteúdo será fornecido como entrada
  • A entrada válida será adicionada ao Chaty do Grupo de Agentes como uma mensagem de Usuário.
Console.WriteLine();
Console.Write("> ");
string input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input))
{
    continue;
}
input = input.Trim();
if (input.Equals("EXIT", StringComparison.OrdinalIgnoreCase))
{
    isComplete = true;
    break;
}

if (input.Equals("RESET", StringComparison.OrdinalIgnoreCase))
{
    await chat.ResetAsync();
    Console.WriteLine("[Converation has been reset]");
    continue;
}

if (input.StartsWith("@", StringComparison.Ordinal) && input.Length > 1)
{
    string filePath = input.Substring(1);
    try
    {
        if (!File.Exists(filePath))
        {
            Console.WriteLine($"Unable to access file: {filePath}");
            continue;
        }
        input = File.ReadAllText(filePath);
    }
    catch (Exception)
    {
        Console.WriteLine($"Unable to access file: {filePath}");
        continue;
    }
}

chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input));
user_input = input("User:> ")
if not user_input:
    continue

if user_input.lower() == "exit":
    is_complete = True
    break

if user_input.lower() == "reset":
    await chat.reset()
    print("[Conversation has been reset]")
    continue

if user_input.startswith("@") and len(input) > 1:
    file_path = input[1:]
    try:
        if not os.path.exists(file_path):
            print(f"Unable to access file: {file_path}")
            continue
        with open(file_path) as file:
            user_input = file.read()
    except Exception:
        print(f"Unable to access file: {file_path}")
        continue

await chat.add_chat_message(ChatMessageContent(role=AuthorRole.USER, content=user_input))

Os agentes estão atualmente indisponíveis em Java.

Para iniciar a colaboração do Agente em resposta à entrada do usuário e exibir as respostas do Agente , invoque o Chat do Grupo do Agente, no entanto, primeiro certifique-se de redefinir o estado de Conclusão a partir de qualquer chamada anterior.

Nota: Falhas de serviço estão sendo detetadas e exibidas para evitar travar o loop de conversa.

chat.IsComplete = false;

try
{
    await foreach (ChatMessageContent response in chat.InvokeAsync())
    {
        Console.WriteLine();
        Console.WriteLine($"{response.AuthorName.ToUpperInvariant()}:{Environment.NewLine}{response.Content}");
    }
}
catch (HttpOperationException exception)
{
    Console.WriteLine(exception.Message);
    if (exception.InnerException != null)
    {
        Console.WriteLine(exception.InnerException.Message);
        if (exception.InnerException.Data.Count > 0)
        {
            Console.WriteLine(JsonSerializer.Serialize(exception.InnerException.Data, new JsonSerializerOptions() { WriteIndented = true }));
        }
    }
}
chat.is_complete = False
async for response in chat.invoke():
    print(f"# {response.role} - {response.name or '*'}: '{response.content}'")

if chat.is_complete:
    is_complete = True
    break

Os agentes estão atualmente indisponíveis em Java.

Final

Juntando todas as etapas, temos o código final para este exemplo. A implementação completa é fornecida abaixo.

// Copyright (c) Microsoft. All rights reserved.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using Azure.Identity;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Chat;
using Microsoft.SemanticKernel.Agents.History;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;

namespace AgentsSample;

public static class Program
{
    public static async Task Main()
    {
        // Load configuration from environment variables or user secrets.
        Settings settings = new();

        Console.WriteLine("Creating kernel...");
        IKernelBuilder builder = Kernel.CreateBuilder();

        builder.AddAzureOpenAIChatCompletion(
            settings.AzureOpenAI.ChatModelDeployment,
            settings.AzureOpenAI.Endpoint,
            new AzureCliCredential());

        Kernel kernel = builder.Build();

        Kernel toolKernel = kernel.Clone();
        toolKernel.Plugins.AddFromType<ClipboardAccess>();


        Console.WriteLine("Defining agents...");

        const string ReviewerName = "Reviewer";
        const string WriterName = "Writer";

        ChatCompletionAgent agentReviewer =
            new()
            {
                Name = ReviewerName,
                Instructions =
                    """
                    Your responsiblity is to review and identify how to improve user provided content.
                    If the user has providing input or direction for content already provided, specify how to address this input.
                    Never directly perform the correction or provide example.
                    Once the content has been updated in a subsequent response, you will review the content again until satisfactory.
                    Always copy satisfactory content to the clipboard using available tools and inform user.

                    RULES:
                    - Only identify suggestions that are specific and actionable.
                    - Verify previous suggestions have been addressed.
                    - Never repeat previous suggestions.
                    """,
                Kernel = toolKernel,
                Arguments = new KernelArguments(new AzureOpenAIPromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() })
            };

        ChatCompletionAgent agentWriter =
            new()
            {
                Name = WriterName,
                Instructions =
                    """
                    Your sole responsiblity is to rewrite content according to review suggestions.

                    - Always apply all review direction.
                    - Always revise the content in its entirety without explanation.
                    - Never address the user.
                    """,
                Kernel = kernel,
            };

        KernelFunction selectionFunction =
            AgentGroupChat.CreatePromptFunctionForStrategy(
                $$$"""
                Examine the provided RESPONSE and choose the next participant.
                State only the name of the chosen participant without explanation.
                Never choose the participant named in the RESPONSE.

                Choose only from these participants:
                - {{{ReviewerName}}}
                - {{{WriterName}}}

                Always follow these rules when choosing the next participant:
                - If RESPONSE is user input, it is {{{ReviewerName}}}'s turn.
                - If RESPONSE is by {{{ReviewerName}}}, it is {{{WriterName}}}'s turn.
                - If RESPONSE is by {{{WriterName}}}, it is {{{ReviewerName}}}'s turn.

                RESPONSE:
                {{$lastmessage}}
                """,
                safeParameterNames: "lastmessage");

        const string TerminationToken = "yes";

        KernelFunction terminationFunction =
            AgentGroupChat.CreatePromptFunctionForStrategy(
                $$$"""
                Examine the RESPONSE and determine whether the content has been deemed satisfactory.
                If content is satisfactory, respond with a single word without explanation: {{{TerminationToken}}}.
                If specific suggestions are being provided, it is not satisfactory.
                If no correction is suggested, it is satisfactory.

                RESPONSE:
                {{$lastmessage}}
                """,
                safeParameterNames: "lastmessage");

        ChatHistoryTruncationReducer historyReducer = new(1);

        AgentGroupChat chat =
            new(agentReviewer, agentWriter)
            {
                ExecutionSettings = new AgentGroupChatSettings
                {
                    SelectionStrategy =
                        new KernelFunctionSelectionStrategy(selectionFunction, kernel)
                        {
                            // Always start with the editor agent.
                            InitialAgent = agentReviewer,
                            // Save tokens by only including the final response
                            HistoryReducer = historyReducer,
                            // The prompt variable name for the history argument.
                            HistoryVariableName = "lastmessage",
                            // Returns the entire result value as a string.
                            ResultParser = (result) => result.GetValue<string>() ?? agentReviewer.Name
                        },
                    TerminationStrategy =
                        new KernelFunctionTerminationStrategy(terminationFunction, kernel)
                        {
                            // Only evaluate for editor's response
                            Agents = [agentReviewer],
                            // Save tokens by only including the final response
                            HistoryReducer = historyReducer,
                            // The prompt variable name for the history argument.
                            HistoryVariableName = "lastmessage",
                            // Limit total number of turns
                            MaximumIterations = 12,
                            // Customer result parser to determine if the response is "yes"
                            ResultParser = (result) => result.GetValue<string>()?.Contains(TerminationToken, StringComparison.OrdinalIgnoreCase) ?? false
                        }
                }
            };

        Console.WriteLine("Ready!");

        bool isComplete = false;
        do
        {
            Console.WriteLine();
            Console.Write("> ");
            string input = Console.ReadLine();
            if (string.IsNullOrWhiteSpace(input))
            {
                continue;
            }
            input = input.Trim();
            if (input.Equals("EXIT", StringComparison.OrdinalIgnoreCase))
            {
                isComplete = true;
                break;
            }

            if (input.Equals("RESET", StringComparison.OrdinalIgnoreCase))
            {
                await chat.ResetAsync();
                Console.WriteLine("[Converation has been reset]");
                continue;
            }

            if (input.StartsWith("@", StringComparison.Ordinal) && input.Length > 1)
            {
                string filePath = input.Substring(1);
                try
                {
                    if (!File.Exists(filePath))
                    {
                        Console.WriteLine($"Unable to access file: {filePath}");
                        continue;
                    }
                    input = File.ReadAllText(filePath);
                }
                catch (Exception)
                {
                    Console.WriteLine($"Unable to access file: {filePath}");
                    continue;
                }
            }

            chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input));

            chat.IsComplete = false;

            try
            {
                await foreach (ChatMessageContent response in chat.InvokeAsync())
                {
                    Console.WriteLine();
                    Console.WriteLine($"{response.AuthorName.ToUpperInvariant()}:{Environment.NewLine}{response.Content}");
                }
            }
            catch (HttpOperationException exception)
            {
                Console.WriteLine(exception.Message);
                if (exception.InnerException != null)
                {
                    Console.WriteLine(exception.InnerException.Message);
                    if (exception.InnerException.Data.Count > 0)
                    {
                        Console.WriteLine(JsonSerializer.Serialize(exception.InnerException.Data, new JsonSerializerOptions() { WriteIndented = true }));
                    }
                }
            }
        } while (!isComplete);
    }

    private sealed class ClipboardAccess
    {
        [KernelFunction]
        [Description("Copies the provided content to the clipboard.")]
        public static void SetClipboard(string content)
        {
            if (string.IsNullOrWhiteSpace(content))
            {
                return;
            }

            using Process clipProcess = Process.Start(
                new ProcessStartInfo
                {
                    FileName = "clip",
                    RedirectStandardInput = true,
                    UseShellExecute = false,
                });

            clipProcess.StandardInput.Write(content);
            clipProcess.StandardInput.Close();
        }
    }
}

# Copyright (c) Microsoft. All rights reserved.

import asyncio
import os

from semantic_kernel.agents import AgentGroupChat, ChatCompletionAgent
from semantic_kernel.agents.strategies.selection.kernel_function_selection_strategy import (
    KernelFunctionSelectionStrategy,
)
from semantic_kernel.agents.strategies.termination.kernel_function_termination_strategy import (
    KernelFunctionTerminationStrategy,
)
from semantic_kernel.connectors.ai.open_ai.services.azure_chat_completion import AzureChatCompletion
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.functions.kernel_function_decorator import kernel_function
from semantic_kernel.functions.kernel_function_from_prompt import KernelFunctionFromPrompt
from semantic_kernel.kernel import Kernel

###################################################################
# The following sample demonstrates how to create a simple,       #
# agent group chat that utilizes a Reviewer Chat Completion       #
# Agent along with a Writer Chat Completion Agent to              #
# complete a user's task.                                         #
###################################################################


class ClipboardAccess:
    @kernel_function
    def set_clipboard(content: str):
        if not content.strip():
            return

        pyperclip.copy(content)


REVIEWER_NAME = "Reviewer"
COPYWRITER_NAME = "Writer"


def _create_kernel_with_chat_completion(service_id: str) -> Kernel:
    kernel = Kernel()
    kernel.add_service(AzureChatCompletion(service_id=service_id))
    return kernel


async def main():
    agent_reviewer = ChatCompletionAgent(
        service_id=REVIEWER_NAME,
        kernel=_create_kernel_with_chat_completion(REVIEWER_NAME),
        name=REVIEWER_NAME,
        instructions="""
            Your responsiblity is to review and identify how to improve user provided content.
            If the user has providing input or direction for content already provided, specify how to 
            address this input.
            Never directly perform the correction or provide example.
            Once the content has been updated in a subsequent response, you will review the content 
            again until satisfactory.
            Always copy satisfactory content to the clipboard using available tools and inform user.

            RULES:
            - Only identify suggestions that are specific and actionable.
            - Verify previous suggestions have been addressed.
            - Never repeat previous suggestions.
            """,
    )

    agent_writer = ChatCompletionAgent(
        service_id=COPYWRITER_NAME,
        kernel=_create_kernel_with_chat_completion(COPYWRITER_NAME),
        name=COPYWRITER_NAME,
        instructions="""
            Your sole responsiblity is to rewrite content according to review suggestions.

            - Always apply all review direction.
            - Always revise the content in its entirety without explanation.
            - Never address the user.
            """,
    )

    selection_function = KernelFunctionFromPrompt(
        function_name="selection",
        prompt=f"""
        Determine which participant takes the next turn in a conversation based on the the most recent participant.
        State only the name of the participant to take the next turn.
        No participant should take more than one turn in a row.

        Choose only from these participants:
        - {REVIEWER_NAME}
        - {COPYWRITER_NAME}

        Always follow these rules when selecting the next participant:
        - After user input, it is {COPYWRITER_NAME}'s turn.
        - After {COPYWRITER_NAME} replies, it is {REVIEWER_NAME}'s turn.
        - After {REVIEWER_NAME} provides feedback, it is {COPYWRITER_NAME}'s turn.

        History:
        {{{{$history}}}}
        """,
    )

    TERMINATION_KEYWORD = "yes"

    termination_function = KernelFunctionFromPrompt(
        function_name="termination",
        prompt=f"""
            Examine the RESPONSE and determine whether the content has been deemed satisfactory.
            If content is satisfactory, respond with a single word without explanation: {TERMINATION_KEYWORD}.
            If specific suggestions are being provided, it is not satisfactory.
            If no correction is suggested, it is satisfactory.

            RESPONSE:
            {{{{$history}}}}
            """,
    )

    chat = AgentGroupChat(
        agents=[agent_writer, agent_reviewer],
        selection_strategy=KernelFunctionSelectionStrategy(
            function=selection_function,
            kernel=_create_kernel_with_chat_completion("selection"),
            result_parser=lambda result: str(result.value[0]) if result.value is not None else COPYWRITER_NAME,
            agent_variable_name="agents",
            history_variable_name="history",
        ),
        termination_strategy=KernelFunctionTerminationStrategy(
            agents=[agent_reviewer],
            function=termination_function,
            kernel=_create_kernel_with_chat_completion("termination"),
            result_parser=lambda result: TERMINATION_KEYWORD in str(result.value[0]).lower(),
            history_variable_name="history",
            maximum_iterations=10,
        ),
    )

    is_complete: bool = False
    while not is_complete:
        user_input = input("User:> ")
        if not user_input:
            continue

        if user_input.lower() == "exit":
            is_complete = True
            break

        if user_input.lower() == "reset":
            await chat.reset()
            print("[Conversation has been reset]")
            continue

        if user_input.startswith("@") and len(input) > 1:
            file_path = input[1:]
            try:
                if not os.path.exists(file_path):
                    print(f"Unable to access file: {file_path}")
                    continue
                with open(file_path) as file:
                    user_input = file.read()
            except Exception:
                print(f"Unable to access file: {file_path}")
                continue

        await chat.add_chat_message(ChatMessageContent(role=AuthorRole.USER, content=user_input))

        async for response in chat.invoke():
            print(f"# {response.role} - {response.name or '*'}: '{response.content}'")

        if chat.is_complete:
            is_complete = True
            break


if __name__ == "__main__":
    asyncio.run(main())

Os agentes estão atualmente indisponíveis em Java.