AgentApplication in Managed Disks

AgentApplication è il blocco centrale di un agente creato con l'Agents SDK. AgentApplication è il punto di ingresso per tutte le attività in ingresso, inclusi i messaggi degli utenti, gli eventi del ciclo di vita della conversazione, le interazioni con schede adattive, i callback OAuth.

Un agente è, nella sua essenza, un oggetto AgentApplication. È possibile configurarlo con i gestori che descrivono le operazioni dell'agente. L'SDK si occupa del routing, della gestione dello stato e dell'infrastruttura necessaria per eseguirlo.

Funzionamento di AgentApplication

Ogni agente ha un ciclo di vita che inizia quando un canale (Microsoft Teams, un servizio bot o un client personalizzato) recapita un'attività all'endpoint dell'agente. AgentApplication si trova al centro del ciclo di vita:

Channel → Hosting layer → AgentApplication → Your handlers

I livelli di elaborazione in un agente creato con Agents SDK funzionano nel modo seguente:

  1. Il livello di hosting riceve la richiesta HTTP e la autentica.
  2. Il AgentApplication gestisce l'attività in ingresso tramite la sua pipeline.
  3. I gestori vengono chiamati in base alle route corrispondenti.

L'agente carica lo stato del turno prima dell'esecuzione delle funzioni di gestione. Successivamente, l'agente salva lo stato del turno.

Concetti di base

Impegni

Nell'Agents SDK, tutto scorre come un'attività. Un'attività è un messaggio strutturato che rappresenta un evento che si è verificato. Un'attività ha un tipo, ad esempio message, event, invoke, conversationUpdate e così via. Trasporta un carico utile pertinente a quel tipo. AgentApplication riceve le attività e le instrada al gestore corretto.

Routes

Una route associa un selettore a un gestore. Il selettore determina se una route corrisponde all'attività corrente. Il gestore esegue la tua logica quando il percorso corrisponde.

Registrare le route quando si configura l'agente. Possono corrispondere a:

  • Messaggio contenente testo specifico o corrispondenza di un'espressione regolare
  • Qualsiasi attività di un determinato tipo
  • Eventi del ciclo di vita della conversazione (membro aggiunto, membro rimosso)
  • Azioni della scheda adattiva
  • Condizioni personalizzate

Quando arriva un'attività, il sistema valuta i percorsi in ordine finché non trova una corrispondenza. Per impostazione predefinita, viene eseguito un solo percorso.

Stato di rotazione

AgentApplication gestisce lo stato _turn, ovvero l'archiviazione strutturata partizionata in ambiti:

Tipo di ambito Description
Conversazione Condiviso tra tutti gli utenti in una conversazione, persistente tra turni
Utente Limitato a un singolo utente in tutte le conversazioni
Temp Solo turno corrente- mai persistente

Il sistema carica automaticamente lo stato prima che i handler siano eseguiti e lo salva in seguito automaticamente.

Contesto di turno

Quando viene eseguito un gestore, riceve un contesto di turno. Il contesto del turno è un'istantanea dell'attività corrente, della connessione dell'adattatore e delle utilità per l'invio di risposte. Il contesto del turno è l'interfaccia dell'interazione corrente.

Middleware

AgentApplication supporta una pipeline middleware. Il middleware è una catena di componenti che elaborano ogni operazione prima e dopo l'esecuzione dei gestori. Il middleware può esaminare, trasformare o interrompere bruscamente il flusso delle attività. Gli usi comuni includono la registrazione, i controlli di autenticazione e la normalizzazione delle richieste.

Creare un agente

Subclassa AgentApplication e registra i gestori nel costruttore. Il framework di hosting inserisce AgentApplicationOptionsautomaticamente .

public class MyAgent : AgentApplication
{
    public MyAgent(AgentApplicationOptions options) : base(options)
    {
        OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeAsync);
        OnActivity(ActivityTypes.Message, OnMessageAsync, rank: RouteRank.Last);
    }

    private async Task WelcomeAsync(ITurnContext context, ITurnState state, CancellationToken ct)
    {
        foreach (var member in context.Activity.MembersAdded)
        {
            if (member.Id != context.Activity.Recipient.Id)
            {
                await context.SendActivityAsync("Hello! How can I help you?", cancellationToken: ct);
            }
        }
    }

    private async Task OnMessageAsync(ITurnContext context, ITurnState state, CancellationToken ct)
    {
        await context.SendActivityAsync($"You said: {context.Activity.Text}", cancellationToken: ct);
    }
}

Registrare l'agente in Program.cs:

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient();
builder.Services.AddSingleton<IStorage, MemoryStorage>();
builder.Services.AddAgent<MyAgent>();
builder.Services.AddAgentAspNetAuthentication(builder.Configuration);

WebApplication app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();
app.MapAgentApplicationEndpoints(requireAuth: !app.Environment.IsDevelopment());

app.Run();

Registrare i gestori di attività

Gestire i messaggi

Trova la corrispondenza dei messaggi in base al testo esatto (senza distinzione tra maiuscole e minuscole):

OnMessage("help", async (context, state, ct) =>
{
    await context.SendActivityAsync("Here's what I can do...", cancellationToken: ct);
});

Trova la corrispondenza dei messaggi usando un'espressione regolare:

OnMessage(new Regex(@"^order\s+\d+$", RegexOptions.IgnoreCase), async (context, state, ct) =>
{
    await context.SendActivityAsync("Looking up your order...", cancellationToken: ct);
});

Gestire gli aggiornamenti delle conversazioni

Registrare i gestori per gli eventi del ciclo di vita della conversazione, ad esempio l'aggiunta o l'uscita dei membri.

OnConversationUpdate(ConversationUpdateEvents.MembersAdded, async (context, state, ct) =>
{
    foreach (var member in context.Activity.MembersAdded)
    {
        if (member.Id != context.Activity.Recipient.Id)
        {
            await context.SendActivityAsync("Welcome!", cancellationToken: ct);
        }
    }
});

OnConversationUpdate(ConversationUpdateEvents.MembersRemoved, async (context, state, ct) =>
{
    // Called when participants leave the conversation
});

Gestire qualsiasi tipo di attività

Confronta qualsiasi attività in base al tipo di stringa per un controllo completo sull'instradamento.

OnActivity(ActivityTypes.Message, async (context, state, ct) =>
{
    // Handles all message activities
});

OnActivity(ActivityTypes.Event, async (context, state, ct) =>
{
    // Handles event activities
});

Usare costanti ActivityTypes anziché stringhe codificate.

Controllare l'ordine di valutazione del percorso

Il sistema ordina le route in un ordine di valutazione fisso quando le si registra, non in fase di esecuzione. L'ordinamento usa due livelli:

  1. Tipo di route: il sistema raggruppa le route per tipo e valuta sempre i tipi con priorità più alta prima dei tipi con priorità più bassa, indipendentemente dalla classificazione:

    Priorità Tipo di route
    1 (più alto) Procedura di invocazione delle route agentiche
    2 Attivare percorsi (azioni della scheda adattiva, callback OAuth e altre chiamate a tempo)
    3 Route agentiche
    4 (più basso) Tutti gli altri percorsi
  2. Classificazione: all'interno di ogni gruppo di tipi di percorsi, il sistema ordina i percorsi in base al loro valore di classificazione. I valori numerici inferiori vengono valutati per primi.

Usare RouteRank le costanti per impostare la classificazione durante la registrazione di un gestore:

Costante Value Significato
RouteRank.First 0 Valutato prima di tutte le altre rotte nel gruppo
RouteRank.Unspecified 32767 Impostazione predefinita quando non viene specificato alcun rango
RouteRank.Last 65535 Valutato dopo tutti gli altri percorsi del suo gruppo

Per impostazione predefinita, la valutazione si arresta alla prima route corrispondente. Usare RouteRank.Last per un fallback onnicomprensivo che gestisce tutto ciò che non corrisponde a una route più specifica.

// Specific handlers use the default rank
OnMessage("status", HandleStatusAsync);
OnMessage("help", HandleHelpAsync);

// Catch-all — handles anything not matched above
OnActivity(ActivityTypes.Message, HandleUnknownMessageAsync, rank: RouteRank.Last);

Turn lifecycle hooks

Registra la logica che viene eseguita a ogni passaggio, prima o dopo la corrispondenza del percorso. Questi hook sono utili per la registrazione, gli aspetti trasversali e la gestione degli errori.

OnBeforeTurn(async (context, state, ct) =>
{
    logger.LogInformation("Turn started: {Type}", context.Activity.Type);
    return true; // Return false to abort the turn
});

OnAfterTurn(async (context, state, ct) =>
{
    logger.LogInformation("Turn completed");
    return true; // Return false to skip state saving
});

OnTurnError(async (context, state, exception, ct) =>
{
    logger.LogError(exception, "Turn error");
    await context.SendActivityAsync("Something went wrong. Please try again.", cancellationToken: ct);
});

Quando OnBeforeTurn restituisce false, il turno viene interrotto e non viene eseguito alcun percorso. Quando OnAfterTurn restituisce false, lo stato del turno non viene salvato.

Usare lo stato di turno

L'agente carica automaticamente lo stato di turno prima dell'esecuzione dei gestori e lo salva in seguito. L'oggetto di stato del turno passato ai gestori consente di accedere ai diversi ambiti, in modo da poter leggere e scrivere dati persistenti tra turni o temporanei per il turno corrente:

  • Ambito della conversazione: per i dati condivisi tra tutti i turni in una conversazione
  • Ambito utente: per i dati per utente
  • Ambito temporaneo: per i dati che devono esistere solo durante il turno corrente
OnActivity(ActivityTypes.Message, async (context, state, ct) =>
{
    // Conversation scope — persisted per conversation
    var count = state.Conversation.GetValue<int>("messageCount", () => 0);
    state.Conversation.SetValue("messageCount", count + 1);

    // User scope — persisted per user
    var name = state.User.GetValue<string>("displayName");

    // Temp scope — current turn only
    state.Temp.SetValue("parsedInput", context.Activity.Text?.Trim());

    await context.SendActivityAsync($"Message #{count + 1}: {context.Activity.Text}", cancellationToken: ct);
});

Note

Usare MemoryStorage per lo sviluppo e il test locali. Per le distribuzioni di produzione, in particolare le distribuzioni in esecuzione in più istanze, usare un provider di archiviazione permanente, ad esempio Azure Cosmos DB o Archiviazione BLOB di Azure. Vedere Usare i provider di archiviazione nell'agente.

Passaggi successivi