Erstellen Sie Agent-Workflows mit Microsoft Agent Framework
Einführung
Wenn Sie meine vorherigen Beiträge zum Agent Framework von Microsoft gelesen haben, wissen Sie, wie Sie einzelne Agenten und sogar Gruppenchats mit mehreren Agenten erstellen. In realen Szenarien benötigen Sie jedoch häufig etwas Strukturierteres – einen Workflow, bei dem Agenten in einer bestimmten Reihenfolge ausführen, Ergebnisse aneinander weitergeben und die Verzweigungslogik basierend auf den Ergebnissen verwalten.
Genau das bieten Ihnen die Workflow-Funktionen im Agent Framework. Anstatt Agenten in einen Gruppenchat zu schicken und zu hoffen, dass sie es herausfinden, definieren Sie explizite Schritte, Abhängigkeiten und Datenflüsse zwischen Agenten.
Was sind Agenten-Workflows?
Stellen Sie sich Agenten-Workflows wie eine Pipeline vor. Jeder Schritt wird von einem spezialisierten Agenten bearbeitet, und die Ausgabe eines Schritts fließt in den nächsten ein. Sie können Schritte nacheinander, parallel oder bedingt auf der Grundlage der Ergebnisse ausführen.
Einige Beispiele:
- Content-Pipeline: Recherche → Entwurf → Überprüfung → Veröffentlichen
- Datenverarbeitung: Extrahieren → Transformieren → Validieren → Laden
- Kundensupport: Klassifizieren → Route → Reagieren → Nachverfolgung
Voraussetzungen
Stellen Sie sicher, dass Sie über die neuesten Agent Framework-Pakete verfügen:
dotnet add package Microsoft.SemanticKernel
dotnet add package Microsoft.SemanticKernel.Agents.Core
Und ein Azure OpenAI- oder OpenAI-Endpunkt konfiguriert.
Aufbau eines sequentiellen Workflows
Lassen Sie uns einen Workflow zur Inhaltserstellung mit drei Agenten erstellen: einem Rechercheur, einem Autor und einem Redakteur.
Definieren der Agenten
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: "gpt-4o",
endpoint: config["AzureOpenAI:Endpoint"],
apiKey: config["AzureOpenAI:ApiKey"])
.Build();
var researcher = new ChatCompletionAgent
{
Name = "Researcher",
Instructions = """
You are a research specialist. Given a topic, find key facts, statistics,
and talking points. Return a structured research brief with bullet points.
Be thorough but concise.
""",
Kernel = kernel
};
var writer = new ChatCompletionAgent
{
Name = "Writer",
Instructions = """
You are a technical blog writer. Given a research brief, write a clear
and engaging blog post. Use a conversational tone, include code examples
where relevant, and structure the post with clear headings.
""",
Kernel = kernel
};
var editor = new ChatCompletionAgent
{
Name = "Editor",
Instructions = """
You are a senior editor. Review the blog post for clarity, accuracy,
grammar, and flow. Return the corrected version with a summary of
changes made at the end.
""",
Kernel = kernel
};
Nacheinander ausführen
Der einfachste Workflow ist sequentiell – jeder Agent verarbeitet die Ausgabe des vorherigen:
using Microsoft.SemanticKernel.Agents.Chat;
using Microsoft.SemanticKernel.ChatCompletion;
async Task<string> RunSequentialWorkflow(string topic)
{
var history = new ChatHistory();
string currentInput = $"Research the following topic: {topic}";
var agents = new[] { researcher, writer, editor };
foreach (var agent in agents)
{
history.AddUserMessage(currentInput);
var response = new System.Text.StringBuilder();
await foreach (var message in agent.InvokeAsync(history))
{
response.Append(message.Content);
}
currentInput = response.ToString();
Console.WriteLine($"✅ {agent.Name} completed");
}
return currentInput;
}
var result = await RunSequentialWorkflow("Blazor render modes in .NET 9");
Console.WriteLine(result);
Jeder Agent erhält den gesammelten Kontext, verarbeitet ihn und das Ergebnis geht zum nächsten Schritt über.
Parallele Ausführung
Manchmal können Agenten unabhängig arbeiten. Beispielsweise möchten Sie möglicherweise mehrere Unterthemen gleichzeitig recherchieren:
async Task<List<string>> RunParallelResearch(string[] topics)
{
var tasks = topics.Select(async topic =>
{
var history = new ChatHistory();
history.AddUserMessage($"Research: {topic}");
var response = new System.Text.StringBuilder();
await foreach (var message in researcher.InvokeAsync(history))
{
response.Append(message.Content);
}
Console.WriteLine($"✅ Research completed: {topic}");
return response.ToString();
});
var results = await Task.WhenAll(tasks);
return results.ToList();
}
var topics = new[]
{
"Blazor SSR streaming",
"Enhanced navigation in .NET 9",
"Render mode boundaries"
};
var briefs = await RunParallelResearch(topics);
Anschließend können Sie alle Forschungsberichte einem einzigen Autorenagenten zuführen, um einen zusammenhängenden Beitrag zu erstellen.
Bedingte Verzweigung
Echte Arbeitsabläufe brauchen Entscheidungen. Vielleicht möchten Sie einen Qualitätsprüfungsschritt, der an den Autor zurückleitet, wenn der Beitrag nicht gut genug ist:
var qualityChecker = new ChatCompletionAgent
{
Name = "QualityChecker",
Instructions = """
You are a quality assurance reviewer. Evaluate the blog post on:
1. Technical accuracy
2. Clarity and readability
3. Completeness
Respond with either:
- "APPROVED" if the post meets all criteria
- "REVISION NEEDED: [specific feedback]" if changes are required
Be strict. Only approve posts that are truly ready to publish.
""",
Kernel = kernel
};
async Task<string> RunWithQualityLoop(string topic, int maxRevisions = 3)
{
var history = new ChatHistory();
// Step 1: Research
history.AddUserMessage($"Research: {topic}");
string research = await InvokeAgent(researcher, history);
// Step 2: Write
history.AddUserMessage(research);
string draft = await InvokeAgent(writer, history);
// Step 3: Quality loop
for (int i = 0; i < maxRevisions; i++)
{
var qaHistory = new ChatHistory();
qaHistory.AddUserMessage($"Review this post:\n\n{draft}");
string qaResult = await InvokeAgent(qualityChecker, qaHistory);
if (qaResult.Contains("APPROVED", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine($"✅ Approved after {i + 1} review(s)");
return draft;
}
Console.WriteLine($"🔄 Revision {i + 1}: {qaResult[..Math.Min(100, qaResult.Length)]}...");
// Send back to writer with feedback
history.AddUserMessage($"Please revise based on this feedback:\n{qaResult}");
draft = await InvokeAgent(writer, history);
}
Console.WriteLine("⚠️ Max revisions reached, returning latest draft");
return draft;
}
async Task<string> InvokeAgent(ChatCompletionAgent agent, ChatHistory history)
{
var response = new System.Text.StringBuilder();
await foreach (var message in agent.InvokeAsync(history))
{
response.Append(message.Content);
}
return response.ToString();
}
Dieses Muster ist super nützlich. Der Qualitätsprüfer fungiert als Tor und der Workflow wird in einer Schleife ausgeführt, bis die Ausgabe gut genug ist oder die maximale Revisionsgrenze erreicht.
Plugins zu Workflow-Agenten hinzufügen
Agenten in Workflows können Plugins genauso verwenden wie eigenständige Agenten. Hier werden die Dinge wirklich leistungsstark: Agenten können als Teil ihres Workflow-Schritts APIs aufrufen, Datenbanken abfragen oder Dateivorgänge ausführen:
var researcherWithTools = new ChatCompletionAgent
{
Name = "Researcher",
Instructions = "Research the topic using available tools. Summarize findings.",
Kernel = kernel
};
// Add a web search plugin
kernel.Plugins.AddFromType<WebSearchPlugin>();
// Add a database plugin
kernel.Plugins.AddFromObject(new DatabasePlugin(connectionString), "Database");
public class WebSearchPlugin
{
[KernelFunction, Description("Search the web for information")]
public async Task<string> SearchAsync(
[Description("The search query")] string query)
{
// Your search implementation
using var httpClient = new HttpClient();
var response = await httpClient.GetStringAsync(
$"https://api.search.example.com?q={Uri.EscapeDataString(query)}");
return response;
}
}
Fehlerbehandlung in Workflows
Wenn Sie mehrere Agenten verketten, wird die Fehlerbehandlung von entscheidender Bedeutung. Sie möchten nicht, dass ein fehlgeschlagener Schritt den gesamten Workflow stillschweigend zum Absturz bringt:
async Task<WorkflowResult> RunResilientWorkflow(string input)
{
var result = new WorkflowResult();
try
{
result.Research = await InvokeAgent(researcher, new ChatHistory(input));
}
catch (Exception ex)
{
result.Errors.Add($"Research failed: {ex.Message}");
return result;
}
try
{
var writeHistory = new ChatHistory(result.Research);
result.Draft = await InvokeAgent(writer, writeHistory);
}
catch (Exception ex)
{
result.Errors.Add($"Writing failed: {ex.Message}");
result.Draft = result.Research; // Fallback to research output
}
result.Success = result.Errors.Count == 0;
return result;
}
class WorkflowResult
{
public string Research { get; set; } = "";
public string Draft { get; set; } = "";
public List<string> Errors { get; set; } = new();
public bool Success { get; set; }
}
Best Practices
Nachdem ich mehrere Agenten-Workflows erstellt habe, habe ich Folgendes gelernt:- Konzentrieren Sie sich auf die Anweisungen der Agenten – jeder Agent sollte eine Sache gut machen. Versuchen Sie nicht, einen Agenten aus einem Schweizer Taschenmesser zu machen.
- Kontext einschränken – geben Sie nicht den gesamten Gesprächsverlauf an jeden Agenten weiter. Geben Sie jedem Schritt nur das, was er braucht.
- Legen Sie Revisionsgrenzen fest – Qualitätsschleifen sind großartig, können aber ewig laufen, wenn Sie nicht aufpassen. Halten Sie immer eine maximale Anzahl an Iterationen ein.
- Alles protokollieren – Agentenausgaben können unvorhersehbar sein. Protokollieren Sie die Eingaben und Ausgaben jedes Schritts zum Debuggen.
- Nutzen Sie die parallele Ausführung mit Bedacht – es beschleunigt die Dinge, aber achten Sie auf Ihre API-Ratenbegrenzungen und Token-Kosten.
- Testen Sie zuerst mit kleineren Modellen – entwickeln und testen Sie Ihre Workflow-Logik mit GPT-3.5, bevor Sie für die Produktion auf GPT-4o umsteigen.
Fazit
Mit Agenten-Workflows können Sie komplexe, mehrstufige KI-Pipelines erstellen, in denen jeder Agent ein Spezialist ist. Beginnen Sie mit sequenziellen Arbeitsabläufen, fügen Sie eine parallele Ausführung hinzu, bei der die Schritte unabhängig sind, und verwenden Sie bedingte Verzweigungen für Qualitäts-Gates. Die Muster sind kombinierbar – sobald Sie den Dreh raus haben, können Sie eine ziemlich ausgefeilte Automatisierung erstellen.
Viel Spaß beim Codieren!