Partager via


Procédure : Coordonner la collaboration de l’agent à l’aide de la conversation de groupe d’agents (expérimentale)

Avertissement

L’infrastructure de l’agent de noyau sémantique est expérimentale, toujours en cours de développement et est susceptible de changer.

Vue d’ensemble

Dans cet exemple, nous allons découvrir comment utiliser La conversation de groupe d’agents pour coordonner la collboration de deux agents différents travaillant à examiner et réécrire le contenu fourni par l’utilisateur. Chaque agent est affecté à un rôle distinct :

  • Réviseur : révisions et orientation vers l’enregistreur.
  • Enregistreur : met à jour le contenu utilisateur en fonction de l’entrée du réviseur .

L’approche sera décomposée pas à pas pour éclairer les principales parties du processus de codage.

Mise en route

Avant de continuer avec le codage des fonctionnalités, vérifiez que votre environnement de développement est entièrement configuré et configuré.

Commencez par créer un projet console . Ensuite, incluez les références de package suivantes pour vous assurer que toutes les dépendances requises sont disponibles.

Pour ajouter des dépendances de package à partir de la ligne de commande, utilisez la dotnet commande :

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

Si vous gérez des packages NuGet dans Visual Studio, vérifiez Include prerelease qu’il est vérifié.

Le fichier projet (.csproj) doit contenir les définitions suivantes PackageReference :

  <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>

Agent Framework est expérimental et nécessite une suppression d’avertissement. Cela peut être traité en tant que propriété dans le fichier projet (.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

Les agents sont actuellement indisponibles en Java.

Configuration

Cet exemple nécessite un paramètre de configuration pour se connecter aux services distants. Vous devez définir des paramètres pour 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"

La classe suivante est utilisée dans tous les exemples agent. Veillez à l’inclure dans votre projet pour garantir une fonctionnalité appropriée. Cette classe sert de composant fondamental pour les exemples qui suivent.

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();
    }
}

Le moyen le plus rapide de bien démarrer avec la configuration appropriée pour exécuter l’exemple de code consiste à créer un .env fichier à la racine de votre projet (où votre script est exécuté).

Configurez les paramètres suivants dans votre .env fichier pour 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=""

Une fois configurées, les classes de service IA respectives récupèrent les variables requises et les utilisent pendant l’instanciation.

Les agents sont actuellement indisponibles en Java.

Codage

Le processus de codage de cet exemple implique :

  1. Configuration : initialisation des paramètres et du plug-in.
  2. Définition de l’agent : créez les deux instances de l’agent de saisie semi-automatique de conversation (réviseur et enregistreur).
  3. Définition de conversation : créez la conversation de groupe d’agents et les stratégies associées.
  4. Boucle de conversation : écrivez la boucle qui pilote l’interaction utilisateur/agent.

L’exemple de code complet est fourni dans la section Finale . Reportez-vous à cette section pour l’implémentation complète.

Programme d’installation

Avant de créer un agent d’achèvement de conversation, les paramètres de configuration, les plug-ins et le noyau doivent être initialisés.

Instanciez la Settings classe référencée dans la section Configuration précédente.

Settings settings = new();

Les agents sont actuellement indisponibles en Java.

Initialisez maintenant une Kernel instance avec un IChatCompletionService.

IKernelBuilder builder = Kernel.CreateBuilder();

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

Kernel kernel = builder.Build();

Initialisez l’objet noyau :

kernel = Kernel()

Les agents sont actuellement indisponibles en Java.

Nous allons également créer une deuxième instance de noyau via le clonage et ajouter un plug-in qui permettra à la réécriture de placer le contenu mis à jour sur le presse-papiers.

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

Les agents sont actuellement indisponibles en Java.

Le plug-in Presse-papiers peut être défini dans le cadre de l’exemple.

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();
    }
}

Remarque : nous tirons parti d’un package Python appelé pyperclip. S’il vous plaît installer est à l’aide de pip.

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

        pyperclip.copy(content)

Les agents sont actuellement indisponibles en Java.

Définition de l’agent

Nous allons déclarer les noms de l’agent pour const qu’ils puissent être référencés dans les stratégies de conversation de groupe d’agents :

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

Les agents sont actuellement indisponibles en Java.

La définition de l’agent Réviseur utilise le modèle exploré dans How-To : Chat Completion Agent.

Ici, le réviseur a le rôle de répondre aux entrées utilisateur, de fournir une direction à l’agent Writer et de vérifier le résultat de l’agent 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.
        """,
)

Les agents sont actuellement indisponibles en Java.

L’agent Writer est similaire, mais ne nécessite pas la spécification des paramètres d’exécution, car il n’est pas configuré avec un plug-in.

Ici, l’enregistreur reçoit une tâche à usage unique, suivez la direction et réécrire le contenu.

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.
        """,
)

Les agents sont actuellement indisponibles en Java.

Définition de conversation

La définition de la conversation de groupe d’agents nécessite de prendre en compte les stratégies de sélection du tour de l’agent et de déterminer quand quitter la boucle de conversation. Pour ces deux considérations, nous allons définir une fonction d’invite de noyau.

La première raison de la sélection de l’agent :

L’utilisation AgentGroupChat.CreatePromptFunctionForStrategy fournit un mécanisme pratique pour éviter l’encodage HTML de l’analyseur de message.

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}}}}
    """,
)

Les agents sont actuellement indisponibles en Java.

La deuxième évalue quand quitter la boucle Chat :

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}}}}
        """,
)

Les agents sont actuellement indisponibles en Java.

Ces deux stratégies n’auront besoin que de connaître le message de conversation le plus récent. Cela permet de réduire l’utilisation des jetons et d’améliorer les performances :

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

Les agents sont actuellement indisponibles en Java.

Enfin, nous sommes prêts à rassembler tous les éléments dans notre définition de conversation de groupe d’agents.

La création AgentGroupChat implique :

  1. Incluez les deux agents dans le constructeur.
  2. Définissez une KernelFunctionSelectionStrategy utilisation de l’instance et Kernel de l’instance précédemment définiesKernelFunction.
  3. Définissez une KernelFunctionTerminationStrategy utilisation de l’instance et Kernel de l’instance précédemment définiesKernelFunction.

Notez que chaque stratégie est responsable de l’analyse du KernelFunction résultat.

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!");

La création AgentGroupChat implique :

  1. Incluez les deux agents dans le constructeur.
  2. Définissez une KernelFunctionSelectionStrategy utilisation de l’instance et Kernel de l’instance précédemment définiesKernelFunction.
  3. Définissez une KernelFunctionTerminationStrategy utilisation de l’instance et Kernel de l’instance précédemment définiesKernelFunction.

Notez que chaque stratégie est responsable de l’analyse du KernelFunction résultat.

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,
    ),
)

Les agents sont actuellement indisponibles en Java.

Boucle de conversation

Enfin, nous sommes en mesure de coordonner l’interaction entre l’utilisateur et la conversation du groupe d’agents. Commencez par créer une boucle vide.

Remarque : Contrairement aux autres exemples, aucun historique externe ou thread n’est géré. La conversation de groupe d’agents gère l’historique des conversations en interne.

bool isComplete = false;
do
{

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

Les agents sont actuellement indisponibles en Java.

Nous allons maintenant capturer l’entrée utilisateur dans la boucle précédente. Dans ce cas :

  • L’entrée vide est ignorée
  • Le terme EXIT signale que la conversation est terminée
  • Le terme RESET efface l’historique des conversations du groupe d’agents
  • Tout terme commençant par @ sera traité comme un chemin d’accès de fichier dont le contenu sera fourni comme entrée
  • Une entrée valide est ajoutée au chaty du groupe d’agents en tant que message utilisateur .
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))

Les agents sont actuellement indisponibles en Java.

Pour initifier la collaboration de l’agent en réponse à l’entrée de l’utilisateur et afficher les réponses de l’agent, appelez la conversation du groupe d’agents . Toutefois, veillez d’abord à réinitialiser l’état d’achèvement à partir d’un appel antérieur.

Remarque : Les échecs de service sont interceptés et affichés pour éviter de bloquer la boucle de conversation.

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

Les agents sont actuellement indisponibles en Java.

Finale

Rassembler toutes les étapes, nous avons le code final de cet exemple. L’implémentation complète est fournie ci-dessous.

// 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())

Les agents sont actuellement indisponibles en Java.