AgentApplication no SDK de Agentes do Microsoft 365

AgentApplication é o bloco modular central de um agente criado com o SDK de Agentes. O AgentApplication é o ponto de entrada para toda a atividade recebida, incluindo mensagens de utilizadores, eventos do ciclo de vida da conversação, interações com cartões adaptativos, chamada de retorno de OAuth.

Um agente é, na sua essência, um AgentApplication. Configura-se com processadores que descrevem o que o agente faz. O SDK cuida do encaminhamento, da gestão do estado e da infraestrutura necessária para o funcionamento do agente.

Funcionamento do AgentApplication

Cada agente inicia o seu ciclo de vida quando um canal (Microsoft Teams, um Bot Service ou um cliente personalizado) entrega uma atividade ao ponto final do seu agente. AgentApplication está no centro deste ciclo de vida:

Channel → Hosting layer → AgentApplication → Your handlers

As camadas de processamento num agente criado com o SDK de Agentes funcionam da seguinte forma:

  1. A camada de alojamento recebe a requisição HTTP e autentica-a.
  2. O AgentApplication processa a atividade de entrada através do seu pipeline.
  3. Os seus processadores são chamados com base nas rotas correspondentes.

O seu agente carrega o estado do turno antes de os processadores serem executados. Em seguida, o agente guarda o estado do turno.

Conceitos-chave

Atividades

Tudo no SDK de Agentes flui como uma atividade. Uma atividade é uma mensagem estruturada que representa algo que aconteceu. Uma atividade tem um tipo, como mensagem, evento, invocação, conversationUpdate e assim por diante. Transporta um payload relevante para esse tipo. AgentApplication recebe as atividades e encaminha-as para o processador certo.

Rotas

Uma rota emparelha um seletor com um processador. O seletor determina se uma rota corresponde à atividade atual. O processador executa a sua lógica quando a rota corresponde.

Registe rotas ao configurar o agente. Podem combinar:

  • Uma mensagem contendo texto específico ou correspondendo a uma expressão regular
  • Qualquer atividade de um tipo específico
  • Eventos do ciclo de vida da conversação (membro adicionado, membro removido)
  • Ações do Cartão Adaptativo
  • Condições personalizadas

Quando uma atividade chega, o sistema avalia as rotas sequencialmente até encontrar uma correspondência. Por predefinição, só é executada uma rota.

Estado do turno

O AgentApplication gere o _turn state—um armazenamento estruturado particionado em âmbitos:

Tipo de âmbito Descrição
Conversa Partilhado entre todos os utilizadores numa conversação, persistindo entre turnos
Utilizador Limitado a um utilizador em todas as conversas
Temp Só no turno atual - nunca persiste

O sistema carrega automaticamente o estado antes de os seus processadores serem executados e guarda‑o automaticamente depois.

Contexto do turno

Quando um processador é executado, recebe um contexto de turno. O contexto do turno é um instantâneo da atividade atual, da ligação do adaptador e dos utilitários para enviar respostas. O contexto do turno é a sua interface para a interação atual.

Middleware

AgentApplication suporta um pipeline de middleware. O middleware é uma cadeia de componentes que processa cada turno antes e depois da execução dos seus processadores. O middleware pode inspecionar, transformar ou interromper o fluxo de atividade. Utilizações comuns incluem registos, verificações de autenticação e normalização de pedidos.

Criar um agente

Crie uma subclasse de AgentApplication e registe os seus processadores no construtor. A estrutura de alojamento injeta AgentApplicationOptions automaticamente.

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

Registe o seu agente em 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();

Registe os processadores de atividades

Processar mensagens

Compare mensagens por texto exato (sensível a maiúsculas e minúsculas):

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

Fazer corresponder mensagens através de uma expressão regular:

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

Gerir atualizações de conversação

Registe processadores para eventos do ciclo de vida da conversação, como a entrada ou saída de membros.

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

Lidar com qualquer tipo de atividade

Faça corresponder qualquer atividade pelo sua cadeia de tipo para obter controlo total sobre o encaminhamento.

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

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

Utilize ActivityTypes constantes em vez de cadeias de carateres codificadas.

Controlar a ordem de avaliação das rotas

O sistema ordena as rotas numa ordem fixa de avaliação quando as regista, não em runtime. A ordenação utiliza dois níveis:

  1. Tipo de rota: o sistema agrupa as rotas por tipo e avalia sempre tipos de prioridade mais alta antes dos tipos de prioridade mais baixa, independentemente da classificação:

    Prioridade Tipo de rota
    1 (mais alta) Rotas de invocação por meio de agentes
    2 Invocar rotas (ações de cartões adaptativos, chamadas de retorno de OAuth e outras invocações sensíveis ao tempo)
    3 Rotas por meio de agentes
    4 (mais baixa) Todas as outras rotas
  2. Classificação: em cada grupo de tipos de rotas, o sistema ordena as rotas pelo seu valor de classificação. Os valores numéricos mais baixos são avaliados primeiro.

Use as constantes RouteRank para definir a classificação ao registar um processador:

Constante valor Significado
RouteRank.First 0 Avaliado antes de todas as outras rotas do seu grupo
RouteRank.Unspecified 32767 Predefinido quando não é especificado nenhuma classificação
RouteRank.Last 65535 Avaliado após todas as outras rotas do seu grupo

Por predefinição, a avaliação termina na primeira rota correspondente. Utilize RouteRank.Last como uma contingência global que abrange tudo aquilo que não for correspondido por uma rota mais específica.

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

Hooks do ciclo de vida do turno

Registe lógica que é executada em cada turno, antes ou depois da correspondência de rotas. Estes hooks são úteis para registo, preocupações transversais e processamento de erros.

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 devolve false, o turno é abortado e nenhuma rota é executada. Quando OnAfterTurn devolve false, o estado de turno não é guardado.

Utilizar o estado do turno

O agente carrega automaticamente o estado do turno antes de os seus processadores serem executados e guarda‑o depois. O objeto de estado de turno passado aos seus processadores permite aceder aos diferentes âmbitos, para que possa ler e escrever dados que persistem ao longo dos turnos ou que são efémeros para o turno atual:

  • Âmbito da conversa: para dados partilhados em todos os turnos de uma conversa
  • Âmbito do utilizador: Para dados de cada utilizador
  • Âmbito temporário: para dados que só precisam de existir durante o turno atual
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);
});

Nota

Utilize MemoryStorage para desenvolvimento e testes locais. Para implementações em produção, especialmente implementações a correr em múltiplas instâncias, utilize um fornecedor de armazenamento persistente como Azure Cosmos DB ou Armazenamento de Blobs do Azure. Consulte Utilizar fornecedores de armazenamento no agente

Passos seguintes