AgentApplication в пакете SDK агентов Microsoft 365

AgentApplication является центральным шаблоном агента, построенного с помощью пакета SDK агентов. AgentApplication является точкой входа для всей входящей активности, включая сообщения от пользователей, события жизненного цикла беседы, взаимодействия с адаптивной карточкой, обратные вызовы OAuth.

Агент по своей сути является AgentApplication. Вы настраиваете его с помощью обработчиков, которые определяют поведение вашего агента. SDK отвечает за маршрутизацию, управление состоянием и инфраструктуру, необходимую для работы агента.

Как работает AgentApplication

У каждого агента есть жизненный цикл, который начинается, когда канал (Microsoft Teams, служба Bot или пользовательский клиент) доставляет активность на конечную точку вашего агента. AgentApplication находится в центре этого жизненного цикла:

Channel → Hosting layer → AgentApplication → Your handlers

Слои обработки в агенте, созданном с помощью пакета SDK агентов, работают следующим образом:

  1. Хостинговый слой принимает HTTP-запрос и аутентифицирует его.
  2. AgentApplication обрабатывает входящую активность через свой конвейер.
  3. Ваши обработчики вызываются при совпадении маршрутов.

Ваш агент загружает состояние хода до запуска ваших обработчиков. После этого агент сохраняет состояние текущего хода.

Основные понятия

Действия

В пакете SDK агентов всё обрабатывается в виде действии. Действие — это структурированное сообщение, отражающее произошедшее. У действия есть тип, например: сообщение, событие, вызов, обновление разговора (conversationUpdate) и так далее. Действие содержит полезные данные, соответствующую этому типу. AgentApplication получает действия и направляет их к соответствующему обработчику.

Маршруты

Маршрут совмещает селектор с обработчиком. Селектор определяет, соответствует ли маршрут текущему действию. Обработчик выполняет вашу логику при соответствии маршрута.

Регистрируйте маршруты при настройке агента. Маршруты могут срабатывать на следующее:

  • Сообщение с определенным текстом или соответствующее регулярному выражению
  • Любое действие определенного типа
  • События жизненного цикла беседы (участник добавлен, участник удален)
  • Действия адаптивной карточки
  • Настраиваемые условия

При поступлении действия система проверяет маршруты по очереди, пока не найдет соответствие. По умолчанию выполняется только один маршрут.

Состояние этапа

AgentApplication управляет _turn state — структурированным хранилищем, разделенным на области:

Тип областей Описание
Разговор Общая для всех пользователей в разговоре, сохраняется между этапами
Пользователь Относится к отдельному пользователю во всех разговорах
Временная Только для текущего этапа — не сохраняется

Система автоматически загружает состояние до запуска ваших обработчиков и сохраняет его после.

Контекст этапа

Когда обработчик выполняется, он получает контекст этапа. Контекст этапа — это снимок текущего действия, подключения адаптера и утилит для отправки ответов. Контекст этапа — это ваш интерфейс для текущего взаимодействия.

ПО промежуточного слоя

AgentApplication поддерживает конвейер ПО промежуточного слоя. ПО промежуточного слоя — это цепочка компонентов, обрабатывающих каждое взаимодействие до и после выполнения ваших обработчиков. ПО промежуточного слоя может анализировать, преобразовывать или прерывать поток действий. Распространенные применения включают логирование, проверку аутентификации и нормализацию запросов.

Создание агента

Создайте подкласс AgentApplication и зарегистрируйте обработчики в конструкторе. Хостинговый фреймворк автоматически внедряет AgentApplicationOptions.

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

Зарегистрируйте своего агента в 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();

Зарегистрируйте обработчики действий

Обработка сообщений

Сопоставьте сообщения по точному тексту (без учета регистра):

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

Сопоставление сообщений с помощью регулярного выражения:

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

Обработка обновлений беседы

Регистрируйте обработчики для событий жизненного цикла беседы, таких как присоединение участников или их выход.

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

Обработка действий любого типа

Сопоставление любых действий по строке типа для полного контроля маршрутизации.

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

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

Используйте константы ActivityTypes вместо строк, прописанных в коде.

Контроль порядка оценки маршрутов

Маршруты сортируются в фиксированный порядок оценки при их регистрации, а не во время выполнения. Сортировка включает два уровня:

  1. Тип маршрута: система группирует маршруты по типу и всегда сначала оценивает маршруты более высокого приоритета, чем маршруты низкого приоритета, независимо от ранга:

    Приоритет Тип маршрута
    1 (самый высокий) Маршруты вызовов на основе агентов
    2 Маршруты вызовов (действия адаптивных карточек, обратные вызовы OAuth и другие вызовы, чувствительные ко времени)
    3 Маршруты на основе агентов
    4 (самый низкий) Все остальные маршруты
  2. Ранг: внутри каждой группы типов маршрутов система упорядочивает маршруты по их значению ранга. Маршруты с наименьшими значениями ранга обрабатываются первыми.

Используйте константы RouteRank для назначения ранга при регистрации обработчика:

Константа Значение Значение
RouteRank.First 0 Оценивается перед всеми остальными маршрутами в своей группе
RouteRank.Unspecified 32767 Используется по умолчанию, если ранг не указан
RouteRank.Last 65535 Оценивается после всех остальных маршрутов своей группы

По умолчанию обработка останавливается на первом совпадающем маршруте. Используйте RouteRank.Last для универсального маршрута, который обрабатывает всё, что не охвачено более конкретными маршрутами.

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

Перехватчики жизненного цикла этапа

Регистрируйте логику, которая выполняется на каждом ходе, перед или после сопоставления маршрутов. Эти перехватчики полезны для ведения журналов, сквозных задач и обработки ошибок.

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

Когда OnBeforeTurn возвращает false, выполнение этапа прерывается, и ни один маршрут не запускается. Когда OnAfterTurn возвращает false, состояние этапа не сохраняется.

Использование состояния этапа

Агент автоматически загружает состояние этапа перед выполнением ваших обработчиков и сохраняет его после. Объект состояния этапа, передаваемый вашим обработчикам, предоставляет доступ к различным областям состояния, чтобы вы могли читать и записывать данные, которые сохраняются между этапами или существуют только на текущем этапе:

  • Область разговора: для данных, общих для всех этапов разговора
  • Область пользователя: для данных на каждого пользователя
  • Временная область: для данных, которые должны существовать только на текущем этапе
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);
});

Примечание

Используйте MemoryStorage для локальной разработки и тестирования. Для развертываний в рабочей среде, особенно при работе на нескольких экземплярах, используйте поставщик постоянного хранилища, например Azure Cosmos DB или хранилище BLOB-объектов Azure. См. статью Использование поставщиков хранилища в агенте.

Следующие шаги