AgentApplication en SDK de agentes de Microsoft 365

AgentApplication es el bloque de creación central de un agente compilado con el SDK de agentes. AgentApplication es el punto de entrada de toda la actividad entrante, incluidos los mensajes de los usuarios, los eventos del ciclo de vida de la conversación, las interacciones de tarjetas adaptativas, los callbacks de OAuth.

Un agente es, en esencia, un AgentApplication. Tú lo configuras con controladores que describen lo que hace el agente. El SDK se encarga del enrutamiento, la administración de estado y la infraestructura necesaria para ejecutarlo.

Funcionamiento de AgentApplication

Cada agente tiene un ciclo de vida que se inicia cuando un canal (Microsoft Teams, un servicio de bot o un cliente personalizado) entrega una actividad al punto de conexión del agente. AgentApplication se encuentra en el centro de ese ciclo de vida:

Channel → Hosting layer → AgentApplication → Your handlers

Las capas de procesamiento en un agente compilado con el SDK de agentes funcionan de la siguiente manera:

  1. La capa de hospedaje recibe la solicitud HTTP y la autentica.
  2. AgentApplication procesa la actividad entrante a través de su tubería.
  3. Se llama a los controladores en función de las rutas coincidentes.

El agente carga el estado de turno antes de que se ejecuten los controladores. Después, el agente guarda el estado del turno.

Conceptos principales

Activities

Todo lo que hay en el SDK de agentes fluye como una actividad. Una actividad es un mensaje estructurado que representa algo que ha ocurrido. Una actividad tiene un tipo, como mensaje (message), evento (event), invocar (invoke), actualización de conversación (conversationUpdate), etcétera. Lleva una carga útil relevante para ese tipo. AgentApplication recibe actividades y las enruta al controlador correcto.

Routes

Una ruta empareja un selector con un controlador. El selector determina si una ruta coincide con la actividad actual. El controlador ejecuta tu lógica cuando la ruta coincide.

Registre rutas al configurar el agente. Pueden coincidir con:

  • Mensaje que contiene texto específico o que coincide con una expresión regular
  • Cualquier actividad de un tipo determinado
  • Eventos del ciclo de vida de la conversación (miembro agregado, miembro quitado)
  • Acciones de tarjetas adaptativas
  • Condiciones personalizadas

Cuando llega una actividad, el sistema evalúa las rutas en orden hasta que encuentra una coincidencia. De forma predeterminada, solo se ejecuta una ruta.

Estado de turno

AgentApplication administra el estado de turno: almacenamiento estructurado particionado en ámbitos.

Tipo de ámbito Description
Conversación Compartido para todos los usuarios de una conversación, guardado entre turnos
Usuario Limitado a un usuario individual en todas las conversaciones
Temp Solo turno actual: nunca se conserva

El sistema carga automáticamente el estado antes de que los controladores se ejecuten y lo guarda automáticamente después.

Cambiar contexto

Cuando se ejecuta un controlador, recibe un contexto de turno. El contexto de turno es una instantánea de la actividad actual, la conexión del adaptador y las utilidades para enviar respuestas. El contexto de turno actúa como tu interfaz hacia la interacción en curso.

Middleware

AgentApplication admite una canalización de middleware. El middleware es una cadena de componentes que procesan cada turno antes y después de que se ejecuten los controladores. El middleware puede inspeccionar, transformar o cortocircuitar el flujo de actividad. Entre los usos comunes se incluyen el registro, las comprobaciones de autenticación y la normalización de solicitudes.

Crear un agente

Subclase AgentApplication y registre los controladores en el constructor. El marco de hospedaje inserta AgentApplicationOptionsautomáticamente .

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

Registre el agente en 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();

Registrar controladores de actividad

Gestionar mensajes

Coincidencia de mensajes por texto exacto (sin distinguir entre mayúsculas y minúsculas):

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

Coincidencia de mensajes mediante una expresión regular:

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

Controlar las actualizaciones de conversación

Registre un controlador para los eventos del ciclo de vida de la conversación, como cuando los miembros se unen o salen.

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

Gestionar cualquier tipo de actividad

Coincide con cualquier actividad por su cadena de tipo para un control completo sobre el enrutamiento.

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

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

Use ActivityTypes constantes en lugar de cadenas codificadas de forma rígida.

Controlar el orden de evaluación de rutas

El sistema ordena las rutas en un orden de evaluación fijo al registrarlas, no en tiempo de ejecución. La ordenación usa dos niveles:

  1. Tipo de ruta: el sistema agrupa las rutas por tipo y siempre evalúa los tipos de prioridad superior antes de los tipos de prioridad inferior, independientemente de la clasificación:

    Priority Tipo de ruta
    1 (más alto) Rutas de invocación de agente
    2 Invocar rutas (acciones de tarjeta adaptables, devoluciones de llamada de OAuth y otras invocaciones sensibles al tiempo)
    3 Rutas agénticas
    4 (más bajo) Todas las demás rutas
  2. Rank: dentro de cada grupo de tipos de ruta, el sistema ordena las rutas por su valor de clasificación. Los valores numéricos inferiores se evalúan primero.

Use RouteRank constantes para establecer la clasificación al registrar un controlador:

Constante Value Meaning
RouteRank.First 0 Se evalúa antes de todas las demás rutas de su grupo
RouteRank.Unspecified 32767 Valor predeterminado cuando no se especifica ninguna clasificación
RouteRank.Last 65535 Se evalúa después de todas las demás rutas de su grupo

De forma predeterminada, la evaluación se detiene en la primera ruta coincidente. Se utiliza RouteRank.Last para un mecanismo catch-all que gestiona todo lo que no coincide con una ruta más 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);

Enlaces de ciclo de vida de turnos

Registre la lógica que se ejecuta en cada turno, antes o después de la coincidencia de rutas. Estos enlaces son útiles para el registro, los problemas transversales y el control de errores.

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

Cuando OnBeforeTurn devuelve false, se anula el turno y no se ejecuta ninguna ruta. Cuando OnAfterTurn devuelve false, el estado de turno no se guarda.

Uso del estado de turno

El agente carga automáticamente el estado de turno antes de que los controladores se ejecuten y lo guarde después. El objeto de estado de turno que se pasa a sus manejadores le brinda acceso a los diferentes escopos, lo que le permite leer y escribir datos que se conservan entre turnos o que son efímeros para el turno actual.

  • Ámbito de conversación: para los datos compartidos en todos los turnos de una conversación
  • Ámbito del Usuario: para los datos por usuario
  • Ámbito temporal: para los datos que solo necesitan existir durante el turno actual
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

Se usa MemoryStorage para el desarrollo y las pruebas locales. En el caso de las implementaciones de producción, especialmente las implementaciones que se ejecutan en varias instancias, use un proveedor de almacenamiento persistente, como Azure Cosmos DB o Azure Blob Storage. Consulte Usar proveedores de almacenamiento en su agente.

Pasos siguientes