Freigeben über


Agentische Anwendungsmuster

Es gibt zwei allgemeine Ansätze zum Erstellen agentischer Anwendungen mit KI:

  • Deterministische Workflows – Ihr Code definiert den Steuerungsfluss. Sie schreiben die Abfolge von Schritten, Verzweigungen, Parallelität und Fehlerbehandlung mithilfe von Standardprogrammierungskonstrukten. Das LLM führt innerhalb jedes Schritts Arbeit durch, steuert jedoch nicht den gesamten Fluss.
  • Agentgesteuerte Workflows (Agentschleifen) – Die LLM steuert den Steuerungsfluss. Der Agent entscheidet, welche Tools aufgerufen werden sollen, in welcher Reihenfolge und wann die Aufgabe abgeschlossen ist. Sie stellen Tools und Anweisungen bereit, aber der Agent bestimmt den Ausführungspfad zur Laufzeit.

Beide Ansätze profitieren von dauerhafter Ausführung und können mithilfe des Programmiermodells für dauerhafte Aufgaben implementiert werden. In diesem Artikel wird gezeigt, wie Sie jedes Muster mithilfe von Codebeispielen erstellen.

Tipp

Diese Muster stimmen mit den agentischen Workflow-Designs überein, die in Anthropics Building Effective Agents beschrieben werden. Das Programmmodell für dauerhafte Aufgaben ist diesen Mustern natürlich zugeordnet: Orchestrierungen definieren den Workflowsteuerungsfluss und werden automatisch überprüft, während Aktivitäten nicht deterministische Vorgänge wie LLM-Aufrufe, Toolaufrufe und API-Anforderungen umschließen.

Auswählen eines Ansatzes

In der folgenden Tabelle können Sie entscheiden, wann die einzelnen Ansätze verwendet werden sollen.

Deterministische Workflows verwenden, wenn... Agentschleifen verwenden, wenn...
Die Abfolge der Schritte ist im Voraus bekannt. Die Aufgabe ist offen und die Schritte können nicht vorhergesagt werden.
Sie benötigen explizite Richtlinien für das Verhalten von Agenten. Sie möchten, dass die LLM entscheiden soll, welche Tools und wann verwendet werden sollen.
Compliance oder Auditierbarkeit erfordert einen prüfbaren Kontrollfluss. Der Agent muss seinen Ansatz basierend auf Zwischenergebnissen anpassen.
Sie möchten mehrere KI-Frameworks in einem einzigen Workflow kombinieren. Sie erstellen einen Konversationsagenten mit Werkzeugaufruffunktionen.

Beide Ansätze bieten automatische Checkpoints, Wiederholungsrichtlinien, verteilte Skalierung und Unterstützung von Menschen durch beständige Ausführung.

Deterministische Workflowmuster

In einem deterministischen Workflow steuert Ihr Code den Ausführungspfad. Das LLM wird als Schritt innerhalb des Workflows aufgerufen, entscheidet aber nicht, was als Nächstes geschieht. Das Programmiermodell für dauerhafte Aufgaben ist diesem Ansatz natürlich zugeordnet:

  • Orchestrierungen definieren den Workflowsteuerungsfluss (Sequenz, Verzweigung, Parallelität, Fehlerbehandlung) und werden automatisch überprüft.
  • Aktivitäten umschließen nicht deterministische Vorgänge wie LLM-Aufrufe, Toolaufrufe und API-Anforderungen. Aktivitäten können auf jeder verfügbaren Computeinstanz ausgeführt werden.

In den folgenden Beispielen wird Durable Functions verwendet, der auf Azure Functions mit serverlosem Hosting ausgeführt wird.

In den folgenden Beispielen wird die portable Durable Task SDKs verwendet, die auf jeder Host-Compute ausgeführt werden, einschließlich Azure Container Apps, Kubernetes, virtuellen Computern oder lokal.

Verkettung von Eingabeaufforderungen

Die Verkettung von Eingabeaufforderungen ist das einfachste agentive Muster. Sie unterteilen eine komplexe Aufgabe in eine Reihe sequenzieller LLM-Interaktionen, bei denen die Ausgabe jedes Schritts als Eingabe für den nächsten Schritt dient. Da jeder Aktivitätsaufruf automatisch gespeichert wird, erzwingt ein Absturz mitten in der Pipeline nicht, dass Sie von vorne beginnen und teure LLM-Token erneut verbrauchen – die Ausführung wird ab dem letzten abgeschlossenen Schritt fortgesetzt.

Sie können auch programmgesteuerte Validierungsgates zwischen Schritten einfügen. Beispielsweise können Sie nach dem Generieren einer Gliederung überprüfen, ob sie eine Längen- oder Themeneinschränkung erfüllt, bevor Sie sie an den Entwurfsschritt übergeben.

Dieses Muster ordnet sich direkt dem Funktionskettenmuster im Programmiermodell "Durable Task" zu.

Verwendungsbedingungen: Pipelines zur Inhaltsgenerierung, mehrstufige Dokumentverarbeitung, sequenzielle Datenanreicherung, Workflows, die Zwischenüberprüfungsgates erfordern.

[Function(nameof(PromptChainingOrchestration))]
public async Task<string> PromptChainingOrchestration(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var topic = context.GetInput<string>();

    // Step 1: Generate research outline
    string outline = await context.CallActivityAsync<string>(
        nameof(GenerateOutlineAgent), topic);

    // Step 2: Write first draft from outline
    string draft = await context.CallActivityAsync<string>(
        nameof(WriteDraftAgent), outline);

    // Step 3: Refine and polish the draft
    string finalContent = await context.CallActivityAsync<string>(
        nameof(RefineDraftAgent), draft);

    return finalContent;
}

Hinweis

Der Zustand der Orchestrierung wird bei jeder await Anweisung automatisch überprüft. Wenn der Hostprozess abstürzt oder der virtuelle Computer wiederverwendet wird, wird die Orchestrierung automatisch vom letzten abgeschlossenen Schritt fortgesetzt, anstatt zu beginnen.

[DurableTask]
public class PromptChainingOrchestration : TaskOrchestrator<string, string>
{
    public override async Task<string> RunAsync(
        TaskOrchestrationContext context, string topic)
    {
        // Step 1: Generate research outline
        string outline = await context.CallActivityAsync<string>(
            nameof(GenerateOutlineAgent), topic);

        // Step 2: Write first draft from outline
        string draft = await context.CallActivityAsync<string>(
            nameof(WriteDraftAgent), outline);

        // Step 3: Refine and polish the draft
        string finalContent = await context.CallActivityAsync<string>(
            nameof(RefineDraftAgent), draft);

        return finalContent;
    }
}

Hinweis

Der Zustand der Orchestrierung wird bei jeder await Anweisung automatisch überprüft. Wenn der Hostprozess abstürzt oder der virtuelle Computer wiederverwendet wird, wird die Orchestrierung automatisch vom letzten abgeschlossenen Schritt fortgesetzt, anstatt zu beginnen.

Routing

Routing verwendet einen Klassifizierungsschritt, um zu bestimmen, welcher downstreame Agent oder Modell eine Anforderung verarbeiten soll. Die Orchestrierung ruft zuerst eine Klassifiziereraktivität auf und verzweigt dann basierend auf dem Ergebnis zum entsprechenden Handler. Mit diesem Ansatz können Sie das Eingabeaufforderungs-, Modell- und Toolset jedes Handlers unabhängig anpassen, z. B. Abrechnungsfragen an einen spezialisierten Agenten mit Zugriff auf Zahlungs-APIs weiterleiten und allgemeine Fragen an ein leichteres Modell senden.

Verwendungsbedingungen: Kundensupport-Triage, Absichtsklassifizierung für spezialisierte Agenten, dynamische Modellauswahl basierend auf der Aufgabenkomplexität.

[Function(nameof(RoutingOrchestration))]
public async Task<string> RoutingOrchestration(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var request = context.GetInput<SupportRequest>();

    // Classify the request type
    string category = await context.CallActivityAsync<string>(
        nameof(ClassifyRequestAgent), request.Message);

    // Route to the appropriate specialized agent
    return category switch
    {
        "billing" => await context.CallActivityAsync<string>(
            nameof(BillingAgent), request),
        "technical" => await context.CallActivityAsync<string>(
            nameof(TechnicalSupportAgent), request),
        "general" => await context.CallActivityAsync<string>(
            nameof(GeneralInquiryAgent), request),
        _ => await context.CallActivityAsync<string>(
            nameof(GeneralInquiryAgent), request),
    };
}
[DurableTask]
public class RoutingOrchestration : TaskOrchestrator<SupportRequest, string>
{
    public override async Task<string> RunAsync(
        TaskOrchestrationContext context, SupportRequest request)
    {
        // Classify the request type
        string category = await context.CallActivityAsync<string>(
            nameof(ClassifyRequestAgent), request.Message);

        // Route to the appropriate specialized agent
        return category switch
        {
            "billing" => await context.CallActivityAsync<string>(
                nameof(BillingAgent), request),
            "technical" => await context.CallActivityAsync<string>(
                nameof(TechnicalSupportAgent), request),
            _ => await context.CallActivityAsync<string>(
                nameof(GeneralInquiryAgent), request),
        };
    }
}

Parallelisierung

Wenn Sie über mehrere unabhängige Teilvorgänge verfügen, können Sie sie als parallele Aktivitätsaufrufe verteilen und auf alle Ergebnisse warten, bevor Sie fortfahren. Der "Durable Task Scheduler" verteilt diese Aktivitäten automatisch über alle verfügbaren Computeinstanzen, was bedeutet, dass durch das Hinzufügen weiterer Mitarbeiter direkt die Gesamtzeit der Wanduhr reduziert wird.

Eine gängige Variante ist eine Mehrfachmodellabstimmung: Sie senden die gleiche Aufforderung an mehrere Modelle (oder dasselbe Modell mit unterschiedlichen Temperaturen) parallel, aggregieren oder auswählen aus den Antworten. Da jede parallele Verzweigung unabhängig voneinander überprüft wird, wirkt sich ein vorübergehender Fehler in einer Verzweigung nicht auf die anderen aus.

Dieses Muster ordnet sich direkt dem Lüfterausgangs-/Fan-In-Muster in "Durable Task" zu.

Verwendungsbedingungen: Batchanalyse von Dokumenten, parallelen Toolaufrufen, Multimodellauswertung, Inhaltsmoderation mit mehreren Prüfern.

[Function(nameof(ParallelResearchOrchestration))]
public async Task<string> ParallelResearchOrchestration(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var request = context.GetInput<ResearchRequest>();

    // Fan-out: research multiple subtopics in parallel
    var researchTasks = request.Subtopics
        .Select(subtopic => context.CallActivityAsync<string>(
            nameof(ResearchSubtopicAgent), subtopic))
        .ToList();
    string[] researchResults = await Task.WhenAll(researchTasks);

    // Aggregate: synthesize all research into a single summary
    string summary = await context.CallActivityAsync<string>(
        nameof(SynthesizeAgent),
        new { request.Topic, Research = researchResults });

    return summary;
}
[DurableTask]
public class ParallelResearchOrchestration : TaskOrchestrator<ResearchRequest, string>
{
    public override async Task<string> RunAsync(
        TaskOrchestrationContext context, ResearchRequest request)
    {
        // Fan-out: research multiple subtopics in parallel
        var researchTasks = request.Subtopics
            .Select(subtopic => context.CallActivityAsync<string>(
                nameof(ResearchSubtopicAgent), subtopic))
            .ToList();
        string[] researchResults = await Task.WhenAll(researchTasks);

        // Aggregate: synthesize all research into a single summary
        string summary = await context.CallActivityAsync<string>(
            nameof(SynthesizeAgent),
            new { request.Topic, Research = researchResults });

        return summary;
    }
}

Orchestrator-Arbeiter

In diesem Muster ruft ein zentraler Orchestrator zunächst ein LLM über einen Prozess auf, um die Arbeit zu planen. Basierend auf der LLM-Ausgabe bestimmt der Orchestrator dann, welche Teilvorgänge erforderlich sind. Der Orchestrator verteilt diese Unteraufgaben dann an spezialisierte Arbeits-Orchestrierungen. Der hauptunterschied zur Parallelisierung besteht darin, dass der Satz von Teilvorgängen zur Entwurfszeit nicht festgelegt ist; der Orchestrator bestimmt sie dynamisch zur Laufzeit.

Dieses Muster verwendet Unter-Orchestrierungen, die unabhängig von untergeordneten Workflows überprüft werden. Jede Worker-Orchestrierung kann selbst mehrere Schritte, Wiederholungen und geschachtelte Parallelität enthalten.

Wann verwenden: Umfassende Forschungspipelines, Code-Agent-Workflows, die mehrere Dateien ändern, Zusammenarbeit mehrerer Agenten, bei der jeder Agent eine spezielle Rolle hat.

[Function(nameof(OrchestratorWorkersOrchestration))]
public async Task<string> OrchestratorWorkersOrchestration(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var request = context.GetInput<ResearchRequest>();

    // Central orchestrator: determine what research is needed
    string[] subtasks = await context.CallActivityAsync<string[]>(
        nameof(PlanResearchAgent), request.Topic);

    // Delegate to worker orchestrations in parallel
    var workerTasks = subtasks
        .Select(subtask => context.CallSubOrchestratorAsync<string>(
            nameof(ResearchWorkerOrchestration), subtask))
        .ToList();
    string[] results = await Task.WhenAll(workerTasks);

    // Synthesize results
    string finalReport = await context.CallActivityAsync<string>(
        nameof(SynthesizeAgent),
        new { request.Topic, Research = results });

    return finalReport;
}
[DurableTask]
public class OrchestratorWorkersOrchestration : TaskOrchestrator<ResearchRequest, string>
{
    public override async Task<string> RunAsync(
        TaskOrchestrationContext context, ResearchRequest request)
    {
        // Central orchestrator: determine what research is needed
        string[] subtasks = await context.CallActivityAsync<string[]>(
            nameof(PlanResearchAgent), request.Topic);

        // Delegate to worker orchestrations in parallel
        var workerTasks = subtasks
            .Select(subtask => context.CallSubOrchestratorAsync<string>(
                nameof(ResearchWorkerOrchestration), subtask))
            .ToList();
        string[] results = await Task.WhenAll(workerTasks);

        // Synthesize results
        string finalReport = await context.CallActivityAsync<string>(
            nameof(SynthesizeAgent),
            new { request.Topic, Research = results });

        return finalReport;
    }
}

Evaluator-Optimierer

Das Evaluator-Optimierer-Muster koppelt einen Generator-Agent mit einem Evaluator-Agent in einer Verfeinerungsschleife. Der Generator erzeugt eine Ausgabe, der Evaluator bewertet sie anhand von Qualitätskriterien und liefert Feedback, und die Schleife wiederholt sich, bis die Ausgabe die Kriterien erfüllt oder eine maximale Iterationsanzahl erreicht ist. Da jede Schleifeniteration gespeichert wird, geht bei einem Absturz nach drei erfolgreichen Verfeinerungsrunden dieser Fortschritt nicht verloren.

Dieses Muster ist besonders nützlich, wenn die Qualität programmgesteuert gemessen werden kann, z. B. durch die Validierung, dass der generierte Code kompiliert wird, oder dass eine Übersetzung benannte Entitäten beibehält.

Wann zu verwenden: Codegenerierung mit automatisierter Überprüfung, literarische Übersetzung, iterative Inhaltsverfeinerung, komplexe Suchaufgaben, die mehrere Analyserunden erfordern.

[Function(nameof(EvaluatorOptimizerOrchestration))]
public async Task<string> EvaluatorOptimizerOrchestration(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var request = context.GetInput<ContentRequest>();
    int maxIterations = 5;
    string content = "";
    string feedback = "";

    for (int i = 0; i < maxIterations; i++)
    {
        // Generate or refine content
        content = await context.CallActivityAsync<string>(
            nameof(GenerateContentAgent),
            new { request.Prompt, PreviousContent = content, Feedback = feedback });

        // Evaluate quality
        var evaluation = await context.CallActivityAsync<EvaluationResult>(
            nameof(EvaluateContentAgent), content);

        if (evaluation.MeetsQualityBar)
            return content;

        feedback = evaluation.Feedback;
    }

    return content; // Return best effort after max iterations
}
[DurableTask]
public class EvaluatorOptimizerOrchestration : TaskOrchestrator<ContentRequest, string>
{
    public override async Task<string> RunAsync(
        TaskOrchestrationContext context, ContentRequest request)
    {
        int maxIterations = 5;
        string content = "";
        string feedback = "";

        for (int i = 0; i < maxIterations; i++)
        {
            // Generate or refine content
            content = await context.CallActivityAsync<string>(
                nameof(GenerateContentAgent),
                new { request.Prompt, PreviousContent = content, Feedback = feedback });

            // Evaluate quality
            var evaluation = await context.CallActivityAsync<EvaluationResult>(
                nameof(EvaluateContentAgent), content);

            if (evaluation.MeetsQualityBar)
                return content;

            feedback = evaluation.Feedback;
        }

        return content; // Return best effort after max iterations
    }
}

Agentschleifen

In einer typischen KI-Agent-Implementierung wird eine LLM in einer Schleife aufgerufen, um Tools aufzurufen und Entscheidungen zu treffen, bis die Aufgabe abgeschlossen ist oder eine Beendigungsbedingung erreicht ist. Im Gegensatz zu deterministischen Workflows ist der Ausführungspfad nicht vorherbestimmt. Der Agent bestimmt, was bei jedem Schritt zu tun ist, basierend auf ergebnissen aus vorherigen Schritten.

Agentschleifen eignen sich gut für Vorgänge, bei denen die Anzahl oder Reihenfolge der Schritte nicht vorhergesagt werden kann. Häufige Beispiele sind offene Codierungsagenten, autonome Forschung und Konversations-Bots mit der Fähigkeit, Tools aufzurufen.

Es gibt zwei empfohlene Ansätze für die Implementierung von Agentenschleifen mit dem Durable Task-Programmiermodell.

Vorgehensweise Beschreibung Wann verwenden?
Orchestrierungsbasiert Schreiben Sie die Agentschleife als dauerhafte Orchestrierung. Toolaufrufe werden als Aktivitäten implementiert, und menschliche Eingaben verwenden externe Ereignisse. Die Orchestrierung steuert die Schleifenstruktur, während die LLM die Darin enthaltenen Entscheidungen steuert. Sie benötigen eine differenzierte Kontrolle über die Schleife, Wiederholungsrichtlinien pro Tool, verteilte Lastverteilung bei Toolaufrufen oder die Möglichkeit, die Schleife in Ihrer IDE mit Haltepunkten zu debuggen.
Entitätsbasiert Jede Agentinstanz ist eine dauerhafte Entität. Das Agenten-Framework verwaltet den Loop intern, und die Entität stellt dauerhafte Status- und Sitzungsspeicherung bereit. Sie verwenden ein Agent-Framework (z. B. Microsoft Agent Framework), das die Agentschleife bereits implementiert und sie mit minimalen Codeänderungen haltbarkeitsfähig machen möchten.

Orchestrierungsbasierte Agentenschleifen

Eine orchestrierungsbasierte Agentschleife kombiniert mehrere Durable Task-Funktionen: ewige Orchestrierungen (continue-as-new), um den Speicherverbrauch zu begrenzen, fan-out/fan-in für parallele Toolausführung und externe Ereignisse für Mensch-in-der-Schleife-Interaktionen. Jede Iteration der Schleife:

  1. Sendet den aktuellen Unterhaltungskontext über eine Aktivitäts- oder Zustandsentität an die LLM.
  2. Empfängt die Antwort des LLM, die Toolaufrufe enthalten kann.
  3. Führt alle Toolaufrufe als Aktivitäten aus (verteilt auf verfügbare Compute).
  4. Optional wartet auf die Eingabe von Menschen mithilfe externer Ereignisse.
  5. Die Schleife wird mit dem aktualisierten Zustand fortgesetzt oder vollendet, wenn der Agent signalisiert, dass es beendet ist.
[Function(nameof(AgentLoopOrchestration))]
public async Task<string> AgentLoopOrchestration(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    // Get state from input (supports continue-as-new)
    var state = context.GetInput<AgentState>() ?? new AgentState();

    int maxIterations = 100;
    while (state.Iteration < maxIterations)
    {
        // Send conversation history to the LLM
        var llmResponse = await context.CallActivityAsync<LlmResponse>(
            nameof(CallLlmAgent), state.Messages);

        state.Messages.Add(llmResponse.Message);

        // If the LLM returned tool calls, execute them in parallel
        if (llmResponse.ToolCalls is { Count: > 0 })
        {
            var toolTasks = llmResponse.ToolCalls
                .Select(tc => context.CallActivityAsync<ToolResult>(
                    nameof(ExecuteTool), tc))
                .ToList();
            ToolResult[] toolResults = await Task.WhenAll(toolTasks);

            foreach (var result in toolResults)
                state.Messages.Add(result.ToMessage());
        }
        // If the LLM needs human input, wait for it
        else if (llmResponse.NeedsHumanInput)
        {
            string humanInput = await context.WaitForExternalEvent<string>("HumanInput");
            state.Messages.Add(new Message("user", humanInput));
        }
        // LLM is done
        else
        {
            return llmResponse.FinalAnswer;
        }

        state.Iteration++;

        // Periodically continue-as-new to keep the history bounded
        if (state.Iteration % 10 == 0)
        {
            context.ContinueAsNew(state);
            return null!; // Orchestration will restart with updated state
        }
    }

    return "Max iterations reached.";
}
[DurableTask]
public class AgentLoopOrchestration : TaskOrchestrator<AgentState, string>
{
    public override async Task<string> RunAsync(
        TaskOrchestrationContext context, AgentState? state)
    {
        state ??= new AgentState();

        int maxIterations = 100;
        while (state.Iteration < maxIterations)
        {
            // Send conversation history to the LLM
            var llmResponse = await context.CallActivityAsync<LlmResponse>(
                nameof(CallLlmAgent), state.Messages);

            state.Messages.Add(llmResponse.Message);

            // If the LLM returned tool calls, execute them
            if (llmResponse.ToolCalls is { Count: > 0 })
            {
                var toolTasks = llmResponse.ToolCalls
                    .Select(tc => context.CallActivityAsync<ToolResult>(
                        nameof(ExecuteTool), tc))
                    .ToList();
                ToolResult[] toolResults = await Task.WhenAll(toolTasks);

                foreach (var result in toolResults)
                    state.Messages.Add(result.ToMessage());
            }
            // If the LLM needs human input, wait for it
            else if (llmResponse.NeedsHumanInput)
            {
                string humanInput = await context.WaitForExternalEvent<string>("HumanInput");
                state.Messages.Add(new Message("user", humanInput));
            }
            // LLM is done
            else
            {
                return llmResponse.FinalAnswer;
            }

            state.Iteration++;

            // Periodically continue-as-new to keep the history bounded
            if (state.Iteration % 10 == 0)
            {
                context.ContinueAsNew(state);
                return null!;
            }
        }

        return "Max iterations reached.";
    }
}

Entitätsbasierte Agentschleifen

Wenn Sie ein Agentframework verwenden, das bereits eine eigene Agentschleife implementiert, können Sie es in eine dauerhafte Entität einschließen, um eine Haltbarkeit hinzuzufügen, ohne die Schleifenlogik neu zu schreiben. Jede Entitätsinstanz stellt eine einzelne Agentsitzung dar. Die Entität empfängt Nachrichten, delegiert diese intern an das Agenten-Framework und speichert den Gesprächszustand über mehrere Interaktionen hinweg.

Der Hauptvorteil dieses Ansatzes ist die Einfachheit: Sie schreiben Ihren Agent mit Ihrem bevorzugten Framework und fügen Haltbarkeit als Hosting-Problem hinzu, anstatt den Steuerungsfluss des Agenten neu zu gestalten. Die Entität fungiert als dauerhafter Wrapper, der die Sitzungspersistenz und Wiederherstellung automatisch verwaltet.

Die folgenden Beispiele zeigen, wie Sie ein vorhandenes Agent-SDK als dauerhafte Entität umschließen. Die Entität macht einen message Vorgang verfügbar, den Clients aufrufen, um Benutzereingaben zu senden. Intern delegiert die Entität an das Agent-Framework, das eine eigene Toolaufrufschleife verwaltet.

// Define the entity that wraps an existing agent SDK
public class ChatAgentEntity : TaskEntity<ChatAgentState>
{
    private readonly IChatClient _chatClient;

    public ChatAgentEntity(IChatClient chatClient)
    {
        _chatClient = chatClient;
    }

    // Called by clients to send a message to the agent
    public async Task<string> Message(string userMessage)
    {
        // Add the user message to the conversation history
        State.Messages.Add(new ChatMessage(ChatRole.User, userMessage));

        // Delegate to the agent SDK for the LLM call (with tool loop)
        ChatResponse response = await _chatClient.GetResponseAsync(
            State.Messages, State.Options);

        // Persist the response in the entity state
        State.Messages.AddRange(response.Messages);

        return response.Text;
    }

    // Azure Functions entry point for the entity
    [Function(nameof(ChatAgentEntity))]
    public Task RunEntityAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
    {
        return dispatcher.DispatchAsync<ChatAgentEntity>();
    }
}
// Define the entity that wraps an existing agent SDK
[DurableTask(Name = "ChatAgent")]
public class ChatAgentEntity : TaskEntity<ChatAgentState>
{
    private readonly IChatClient _chatClient;

    public ChatAgentEntity(IChatClient chatClient)
    {
        _chatClient = chatClient;
    }

    // Called by clients to send a message to the agent
    public async Task<string> Message(string userMessage)
    {
        // Add the user message to the conversation history
        State.Messages.Add(new ChatMessage(ChatRole.User, userMessage));

        // Delegate to the agent SDK for the LLM call (with tool loop)
        ChatResponse response = await _chatClient.GetResponseAsync(
            State.Messages, State.Options);

        // Persist the response in the entity state
        State.Messages.AddRange(response.Messages);

        return response.Text;
    }
}

Die erweiterung Durable Task for Microsoft Agent Framework verwendet diesen Ansatz. Er umschließt Microsoft Agent Framework-Agents als dauerhafte Entitäten, stellt persistente Sitzungen, automatische Prüfpunkte und integrierte API-Endpunkte mit einer einzigen Konfigurationszeile bereit.

Nächste Schritte