Agentanwendung im Microsoft 365 Agents SDK

AgentApplication ist der zentrale Baustein eines Agents, der mit dem Agents SDK erstellt wurde. AgentApplication ist der Eingangspunkt für alle eingehenden Aktivitäten, einschließlich Nachrichten von Benutzern, Unterhaltungslaufzeitereignisse, Interaktionen mit adaptiven Karten, OAuth-Rückrufe.

Ein Agent ist im Kern ein AgentApplication. Sie konfigurieren sie mit Handlern, die beschreiben, was Ihr Agent tut. Das SDK kümmert sich um Routing, Zustandsverwaltung und die infrastruktur, die zum Ausführen erforderlich ist.

Funktionsweise von AgentApplication

Jeder Agent verfügt über einen Lebenszyklus, der beginnt, wenn ein Kanal (Microsoft Teams, ein Botdienst oder ein benutzerdefinierter Client) eine Aktivität an den Endpunkt Ihres Agents übermittelt. AgentApplication befindet sich in der Mitte dieses Lebenszyklus:

Channel → Hosting layer → AgentApplication → Your handlers

Die Verarbeitungsebenen in einem Agent, der mit dem Agents SDK erstellt wurde, funktionieren wie folgt:

  1. Die Hostingebene empfängt die HTTP-Anforderung und authentifiziert sie.
  2. Die AgentApplication verarbeitet die eingehende Aktivität durch ihre Pipeline.
  3. Ihre Handler werden basierend auf übereinstimmenden Routen aufgerufen.

Ihr Agent lädt den Turnzustand, bevor die Handler ausgeführt werden. Anschließend speichert der Agent den Turnzustand.

Wichtige Konzepte

Aktivitäten

Alles im Agents SDK fließt als Aktivität. Eine Aktivität ist eine strukturierte Nachricht, die etwas darstellt, das passiert ist. Eine Aktivität hat einen Typ, z. B. Nachricht, Ereignis, Aufruf, conversationUpdate usw. Sie trägt eine Nutzlast, die für diesen Typ relevant ist. AgentApplication empfängt Aktivitäten und leitet sie an den richtigen Handler weiter.

Arbeitspläne

Eine Route koppelt einen Selektor mit einem Handler. Die Auswahl bestimmt, ob eine Route mit der aktuellen Aktivität übereinstimmt. Der Handler führt Ihre Logik aus, wenn die Route übereinstimmt.

Registrieren Sie Routen, wenn Sie Ihren Agent konfigurieren. Sie können übereinstimmen:

  • Eine Nachricht, die einen bestimmten Text enthält oder einem regulären Ausdruck entspricht
  • Jede Aktivität eines bestimmten Typs
  • Kommunikationslebenszyklusereignisse (hinzugefügtes Mitglied, entferntes Mitglied)
  • Aktionen für adaptive Karten
  • Benutzerdefinierte Bedingungen

Wenn eine Aktivität eintrifft, wertet das System Routen in der Reihenfolge aus, bis eine Übereinstimmung gefunden wird. Standardmäßig wird nur eine Route ausgeführt.

Zustand umschalten

AgentApplication verwaltet den _turn-Zustand – ein strukturierter Speicher, der in Bereiche unterteilt ist:

Bereichstyp Beschreibung
Unterhaltung Freigegeben für alle Nutzer in einem Gespräch, gespeichert zwischen Gesprächsrunden
Benutzer Eingeschränkt auf einen einzelnen Benutzer über alle Gespräche hinweg
Temp Nur aktuelle Wendung – nie beibehalten

Das System lädt den Zustand automatisch, bevor die Handler ausgeführt werden, und speichert ihn anschließend automatisch.

Kontext drehen

Wenn ein Handler ausgeführt wird, empfängt er einen Turnkontext. "Turn context" ist eine Momentaufnahme der aktuellen Aktivität, der Adapterverbindung und Hilfsprogramme zum Senden von Antworten. Der Turnkontext ist Ihre Schnittstelle zur aktuellen Interaktion.

Middleware

AgentApplication unterstützt eine Middleware-Pipeline. Middleware ist eine Kette von Komponenten, die jede Phase vor und nach der Ausführung der Handler verarbeiten. Middleware kann den Aktivitätsfluss untersuchen, transformieren oder kurzschließen. Häufige Verwendungen umfassen Protokollierung, Authentifizierungsprüfungen und Anforderungsnormalisierung.

Einen Agent erstellen

Unterklassen Sie AgentApplication und registrieren Sie Ihre Handler im Konstruktor. Das Hostingframework fügt automatisch ein 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);
    }
}

Registrieren Sie Ihren Agenten 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();

Registrieren von Aktivitätshandlern

Nachrichten verarbeiten

Nachrichten nach exaktem Text abgleichen (Groß-/Kleinschreibung wird nicht beachtet):

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

Abgleichen von Nachrichten mithilfe eines regulären Ausdrucks:

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

Behandlung von Gesprächsaktualisierungen

Registrieren Sie Handler für Ereignisse im Lebenszyklus einer Unterhaltung, z. B. das Beitreten oder Verlassen von Mitgliedern.

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

Verwalten eines beliebigen Aktivitätstyps

Stimmen Sie jede Aktivität anhand ihrer Typzeichenfolge ab, um die vollständige Kontrolle über das Routing zu erhalten.

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

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

Verwenden Sie ActivityTypes Konstanten anstelle hartcodierter Zeichenfolgen.

Steuerung der Routenauswertungsreihenfolge

Das System sortiert Routen in eine feste Auswertungsreihenfolge, wenn Sie sie registrieren, nicht zur Laufzeit. Die Sortierung verwendet zwei Ebenen:

  1. Route type: Das System gruppiert Routen nach Typ, und es bewertet stets höher priorisierte Typen vor niedriger priorisierten Typen, unabhängig vom Rang.

    Priorität Routentyp
    1 (höchste) Agentische Aufrufrouten
    2 Aufrufen von Routen (adaptive Kartenaktionen, OAuth-Rückrufe und andere zeitabhängige Aufrufe)
    3 Agentische Routen
    4 (niedrigste) Alle anderen Routen
  2. Rang: Innerhalb jeder Routentypgruppe sortiert das System Routen nach ihrem Rangwert. Niedrigere numerische Werte werden zuerst ausgewertet.

Verwenden Sie RouteRank Konstanten zum Festlegen der Rangfolge beim Registrieren eines Handlers:

Konstante Wert Dies bedeutet
RouteRank.First 0 Vor allen anderen Routen in der Gruppe ausgewertet
RouteRank.Unspecified 32767 Standardeinstellung, wenn kein Rang angegeben wird
RouteRank.Last 65535 Ausgewertet nach allen anderen Routen in der Gruppe

Standardmäßig stoppt die Auswertung an der ersten übereinstimmenden Route. Verwenden Sie RouteRank.Last als einen universellen Fallback, der alles behandelt, was nicht mit einer spezifischeren Route übereinstimmt.

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

Lebenszyklus-Hooks aktivieren

Registrieren Sie Logik, die bei jeder Drehung ausgeführt wird, vor oder nach dem Routenabgleich. Diese Hooks sind nützlich für die Protokollierung, Querschnittsbelange und die Fehlerbehandlung.

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

Wenn OnBeforeTurn zurückkehrt false, wird der Vorgang abgebrochen, und es werden keine Routen durchgeführt. Wenn OnAfterTurnfalse zurückgibt, wird der Zustandswechsel nicht gespeichert.

Verwendung des Drehzustands

Der Agent lädt den Turnzustand automatisch, bevor die Handler ausgeführt werden, und speichert ihn danach. Das an Ihre Handler übergebene Zustandsobjekt der Runde ermöglicht Ihnen den Zugriff auf die verschiedenen Bereiche, sodass Sie Daten lesen und schreiben können, die über Runden bestehen bleiben oder für die aktuelle Runde kurzlebig sind.

  • Unterhaltungsbereich: Für Daten, die über alle Dialogschritte einer Unterhaltung verfügbar sind
  • Benutzerbereich: Für Benutzerdaten
  • Temp Scope: Für Daten, die nur während des aktuellen Durchlaufs existieren müssen
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);
});

Hinweis

Verwenden Sie MemoryStorage für lokale Entwicklung und Tests. Verwenden Sie für Produktionsbereitstellungen, insbesondere bereitstellungen, die auf mehreren Instanzen ausgeführt werden, einen beständigen Speicheranbieter wie Azure Cosmos DB oder Azure Blob Storage. Siehe Verwenden von Speicheranbietern in Ihrem Agent.

Nächste Schritte