Partager via


Exécuteurs

Les exécuteurs sont les blocs de construction fondamentaux qui traitent les messages dans un flux de travail. Ils sont des unités de traitement autonomes qui reçoivent des messages typés, effectuent des opérations et peuvent produire des messages de sortie ou des événements.

Vue d’ensemble

Chaque exécuteur a un identificateur unique et peut gérer des types de messages spécifiques. Les exécuteurs peuvent être :

  • Composants logiques personnalisés : traiter des données, appeler des API ou transformer des messages
  • Agents IA : utilisez des llms pour générer des réponses (voir Agents dans les flux de travail)

Important

La méthode recommandée pour définir des gestionnaires de messages d’exécuteur en C# consiste à utiliser l’attribut [MessageHandler] sur les méthodes d’une partial classe qui dérive de Executor. Cela utilise la génération de code source au moment de la compilation pour l'enregistrement des gestionnaires, ce qui offre de meilleures performances, une validation à la compilation et une compatibilité AOT native.

Structure d’exécuteur de base

Les exécuteurs dérivent de la Executor classe de base et utilisent l’attribut [MessageHandler] pour déclarer des méthodes de gestionnaire. La classe doit être marquée partial pour activer la génération source.

using Microsoft.Agents.AI.Workflows;

internal sealed partial class UppercaseExecutor() : Executor("UppercaseExecutor")
{
    [MessageHandler]
    private ValueTask<string> HandleAsync(string message, IWorkflowContext context)
    {
        string result = message.ToUpperInvariant();
        return ValueTask.FromResult(result); // Return value is automatically sent to connected executors
    }
}

Vous pouvez également envoyer manuellement des messages sans retourner une valeur :

internal sealed partial class UppercaseExecutor() : Executor("UppercaseExecutor")
{
    [MessageHandler]
    private async ValueTask HandleAsync(string message, IWorkflowContext context)
    {
        string result = message.ToUpperInvariant();
        await context.SendMessageAsync(result); // Manually send messages to connected executors
    }
}

Types d’entrée multiples

Gérez plusieurs types d’entrée en définissant plusieurs [MessageHandler] méthodes :

internal sealed partial class SampleExecutor() : Executor("SampleExecutor")
{
    [MessageHandler]
    private ValueTask<string> HandleStringAsync(string message, IWorkflowContext context)
    {
        return ValueTask.FromResult(message.ToUpperInvariant());
    }

    [MessageHandler]
    private ValueTask<int> HandleIntAsync(int message, IWorkflowContext context)
    {
        return ValueTask.FromResult(message * 2);
    }
}

Exécuteurs basés sur des fonctions

Créez un exécuteur à partir d’une fonction à l’aide de la méthode d’extension BindExecutor :

Func<string, string> uppercaseFunc = s => s.ToUpperInvariant();
var uppercase = uppercaseFunc.BindExecutor("UppercaseExecutor");

Structure d’exécuteur de base

Les exécuteurs héritent de la Executor classe de base. Chaque exécuteur utilise des méthodes décorées avec le @handler décorateur. Les gestionnaires doivent avoir des annotations de type appropriées pour spécifier les types de messages qu’ils traitent.

from agent_framework import (
    Executor,
    WorkflowContext,
    handler,
)

class UpperCase(Executor):

    @handler
    async def to_upper_case(self, text: str, ctx: WorkflowContext[str]) -> None:
        """Convert the input to uppercase and forward it to the next node."""
        await ctx.send_message(text.upper())

Exécuteurs basés sur des fonctions

Créez un exécuteur à partir d’une fonction à l’aide du @executor décorateur :

from agent_framework import (
    WorkflowContext,
    executor,
)

@executor(id="upper_case_executor")
async def upper_case(text: str, ctx: WorkflowContext[str]) -> None:
    """Convert the input to uppercase and forward it to the next node."""
    await ctx.send_message(text.upper())

Types d’entrée multiples

Gérez plusieurs types d’entrée en définissant plusieurs gestionnaires :

class SampleExecutor(Executor):

    @handler
    async def to_upper_case(self, text: str, ctx: WorkflowContext[str]) -> None:
        await ctx.send_message(text.upper())

    @handler
    async def double_integer(self, number: int, ctx: WorkflowContext[int]) -> None:
        await ctx.send_message(number * 2)

Paramètres de type explicites

En guise d’alternative aux annotations de type, vous pouvez spécifier des types explicitement via des paramètres décoratifs :

Important

Lorsque vous utilisez des paramètres de type explicites, vous devez spécifier tous les types via le décorateur . Vous ne pouvez pas combiner des paramètres explicites avec des annotations de type. Le input paramètre est obligatoire et outputworkflow_output facultatif.

class ExplicitTypesExecutor(Executor):

    @handler(input=str, output=str)
    async def to_upper_case(self, text, ctx) -> None:
        await ctx.send_message(text.upper())

    @handler(input=str | int, output=str)
    async def handle_mixed(self, message, ctx) -> None:
        await ctx.send_message(str(message).upper())

    @handler(input=str, output=int, workflow_output=bool)
    async def process_with_workflow_output(self, message, ctx) -> None:
        await ctx.send_message(len(message))
        await ctx.yield_output(True)

Objet WorkflowContext

Les WorkflowContext méthodes permettant d’interagir avec le flux de travail pendant l’exécution sont les suivantes :

  • send_message — envoyer des messages à des exécuteurs connectés
  • yield_output — produire des sorties de flux de travail retournées/diffusées en continu vers l’appelant
class OutputExecutor(Executor):

    @handler
    async def handle(self, message: str, ctx: WorkflowContext[Never, str]) -> None:
        await ctx.yield_output("Hello, World!")

Si un gestionnaire n’envoie ni messages ni génère de sorties, aucun paramètre de type n’est nécessaire :

class LogExecutor(Executor):

    @handler
    async def handle(self, message: str, ctx: WorkflowContext) -> None:
        print("Doing some work...")

Prochaines étapes