Udostępnij przez


Egzekutorzy

Funkcje wykonawcze to podstawowe bloki konstrukcyjne, które przetwarzają komunikaty w przepływie pracy. Są to autonomiczne jednostki przetwarzania, które odbierają komunikaty typizowane, wykonują operacje i mogą generować komunikaty wyjściowe lub zdarzenia.

Przegląd

Każdy funkcja wykonawcza ma unikatowy identyfikator i może obsługiwać określone typy komunikatów. Wykonawcy mogą być:

  • Niestandardowe składniki logiki — przetwarzanie danych, wywoływanie interfejsów API lub przekształcanie komunikatów
  • Agenci sztucznej inteligencji — generowanie odpowiedzi przy użyciu modułów LLM (zobacz Agenci w przepływach pracy)

Ważne

Zalecanym sposobem definiowania procedur obsługi komunikatów funkcji wykonawczej w języku C# jest użycie atrybutu [MessageHandler] w metodach w partial klasie pochodzącej z Executorklasy . To używa generowania kodu źródłowego w czasie kompilacji na potrzeby rejestracji obsługi, zapewniając lepszą wydajność, walidację czasu kompilacji i zgodność z natywną funkcją AOT.

Podstawowa struktura funkcji wykonawczej

Funkcje wykonawcze pochodzą z klasy bazowej Executor i używają atrybutu [MessageHandler] do deklarowania metod obsługi. Klasa musi być oznaczona partial w celu włączenia generowania źródła.

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

Komunikaty można również wysyłać ręcznie bez zwracania wartości:

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

Wiele typów danych wejściowych

Obsługa wielu typów danych wejściowych przez zdefiniowanie wielu [MessageHandler] metod:

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

Wykonawcy oparci na funkcjach

Utwórz wykonawcę z funkcji przy użyciu metody rozszerzenia BindExecutor.

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

Podstawowa struktura funkcji wykonawczej

Funkcje wykonawcze dziedziczą z klasy bazowej Executor . Każdy wykonawca używa metod ozdobionych dekoratorem @handler . Programy obsługi muszą mieć właściwe adnotacje typu, aby określić rodzaje komunikatów, które przetwarzają.

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

Funkcyjno-bazowe wykonawce

Tworzenie funkcji wykonawczej na podstawie funkcji przy użyciu dekoratora @executor :

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

Wiele typów danych wejściowych

Obsługa wielu typów danych wejściowych przez zdefiniowanie wielu procedur obsługi:

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)

Jawne parametry typu

Alternatywą dla adnotacji typów jest jawne określenie typów za pomocą parametrów dekoratora:

Ważne

W przypadku używania jawnych parametrów typu należy określić wszystkie typy za pośrednictwem dekoratora — nie można mieszać jawnych parametrów z adnotacjami typów. Parametr input jest wymagany i outputworkflow_output jest opcjonalny.

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)

Obiekt WorkflowContext

WorkflowContext zapewnia metody interakcji z przepływem pracy w trakcie wykonania:

  • send_message — wysyłanie komunikatów do połączonych funkcji wykonawczych
  • yield_output — generowanie danych wyjściowych przepływu pracy zwracanych/przesyłanych strumieniowo do obiektu wywołującego
class OutputExecutor(Executor):

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

Jeśli program obsługi nie wysyła komunikatów ani nie zwraca danych wyjściowych, żaden parametr typu nie jest wymagany:

class LogExecutor(Executor):

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

Dalsze kroki