Microsoft Agent Framework से Agent Workflows बनाना
Introduction
If you’ve read my previous posts on Microsoft’s Agent Framework, you know how to create individual agents and even multi-agent group chats. But in real-world scenarios, you often need something more structured — a workflow where agents execute in a specific order, pass results to each other, and handle branching logic based on outcomes.
That’s exactly what the workflow capabilities in Agent Framework give you. Instead of throwing agents into a group chat and hoping they figure it out, you define explicit steps, dependencies, and data flow between agents.
What are agent workflows?
Think of agent workflows like a pipeline. Each step is handled by a specialized agent, and the output of one step feeds into the next. You can run steps sequentially, in parallel, or conditionally based on results.
Some examples:
- Content pipeline: Research → Draft → Review → Publish
- Data processing: Extract → Transform → Validate → Load
- Customer support: Classify → Route → Respond → Follow-up
Prerequisites
Make sure you have the latest Agent Framework packages:
dotnet add package Microsoft.SemanticKernel
dotnet add package Microsoft.SemanticKernel.Agents.Core
And an Azure OpenAI or OpenAI endpoint configured.
Building a sequential workflow
Let’s build a content creation workflow with three agents: a researcher, a writer, and an editor.
Defining the agents
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
};
Executing sequentially
The simplest workflow is sequential — each agent processes the output of the previous one:
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);
Each agent gets the accumulated context, processes it, and the result moves to the next step.
Parallel execution
Sometimes agents can work independently. For instance, you might want to research multiple sub-topics at the same time:
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);
Then you can feed all the research briefs into a single writer agent to produce one cohesive post.
Conditional branching
Real workflows need decisions. Maybe you want a quality check step that routes back to the writer if the post isn’t good enough:
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();
}
This pattern is super useful. The quality checker acts as a gate, and the workflow loops until the output is good enough or hits the max revision limit.
Adding plugins to workflow agents
Agents in workflows can use plugins just like standalone agents. This is where things get really powerful — agents can call APIs, query databases, or do file operations as part of their workflow step:
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;
}
}
Error handling in workflows
When you’re chaining multiple agents, error handling becomes critical. You don’t want one failed step to crash the entire workflow silently:
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
After building several agent workflows, here’s what I’ve learned:
- Keep agent instructions focused — each agent should do one thing well. Don’t try to make a Swiss Army knife agent.
- Limit context — don’t pass the entire conversation history to every agent. Give each step only what it needs.
- Set revision limits — quality loops are great but can run forever if you’re not careful. Always have a max iterations cap.
- Log everything — agent outputs can be unpredictable. Log every step’s input and output for debugging.
- Use parallel execution wisely — it speeds things up but watch your API rate limits and token costs.
- Test with smaller models first — develop and test your workflow logic with GPT-3.5 before switching to GPT-4o for production.
Conclusion
Agent workflows let you build complex, multi-step AI pipelines where each agent is a specialist. Start with sequential workflows, add parallel execution where steps are independent, and use conditional branching for quality gates. The patterns are composable — once you get the hang of it, you can build some pretty sophisticated automation.
Happy coding!