Microsoft Agent Framework를 사용하여 에이전트 워크플로 구축
소개
Microsoft의 에이전트 프레임워크에 대한 이전 게시물을 읽어보셨다면 개별 에이전트는 물론 다중 에이전트 그룹 채팅을 만드는 방법도 아실 것입니다. 그러나 실제 시나리오에서는 에이전트가 특정 순서로 실행하고, 결과를 서로 전달하고, 결과에 따라 분기 논리를 처리하는 워크플로와 같이 좀 더 구조화된 작업이 필요한 경우가 많습니다.
이것이 바로 Agent Framework의 워크플로 기능이 제공하는 것입니다. 에이전트를 그룹 채팅에 투입하고 에이전트가 해결하기를 바라는 대신 에이전트 간의 명시적인 단계, 종속성 및 데이터 흐름을 정의합니다.
상담사 워크플로란 무엇인가요?
에이전트 워크플로를 파이프라인처럼 생각하세요. 각 단계는 전문 에이전트에 의해 처리되며 한 단계의 출력이 다음 단계로 전달됩니다. 단계를 순차적으로, 병렬로 실행하거나 결과에 따라 조건부로 실행할 수 있습니다.
몇 가지 예:
- 콘텐츠 파이프라인: 연구 → 초안 → 검토 → 게시
- 데이터 처리: 추출 → 변환 → 검증 → 로드
- 고객지원 : 분류 → 전달 → 대응 → 후속 조치
전제 조건
최신 Agent Framework 패키지가 있는지 확인하세요.
dotnet add package Microsoft.SemanticKernel
dotnet add package Microsoft.SemanticKernel.Agents.Core
그리고 Azure OpenAI 또는 OpenAI 엔드포인트가 구성되었습니다.
순차 워크플로 구축
연구원, 작가, 편집자라는 세 명의 에이전트로 콘텐츠 제작 워크플로를 구축해 보겠습니다.
에이전트 정의
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
};
순차적으로 실행 중
가장 간단한 워크플로는 순차적입니다. 각 에이전트는 이전 에이전트의 출력을 처리합니다.
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);
각 에이전트는 축적된 컨텍스트를 가져와 처리하고 그 결과는 다음 단계로 이동합니다.
병렬 실행
때로는 상담원이 독립적으로 작업할 수도 있습니다. 예를 들어, 여러 하위 주제를 동시에 조사하고 싶을 수 있습니다.
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);
그런 다음 모든 연구 개요를 단일 작가 에이전트에 공급하여 하나의 응집력 있는 게시물을 생성할 수 있습니다.
조건부 분기
실제 워크플로에는 결정이 필요합니다. 게시물이 충분하지 않은 경우 작성자에게 다시 전달하는 품질 확인 단계를 원할 수도 있습니다.
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();
}
이 패턴은 매우 유용합니다. 품질 검사기는 게이트 역할을 하며, 출력이 충분하거나 최대 개정 제한에 도달할 때까지 작업 흐름이 반복됩니다.
워크플로 에이전트에 플러그인 추가
워크플로의 에이전트는 독립형 에이전트처럼 플러그인을 사용할 수 있습니다. 에이전트가 워크플로 단계의 일부로 API를 호출하거나, 데이터베이스를 쿼리하거나, 파일 작업을 수행할 수 있는 것은 매우 강력합니다.
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;
}
}
워크플로의 오류 처리
여러 에이전트를 연결하는 경우 오류 처리가 중요해집니다. 하나의 실패한 단계로 인해 전체 워크플로가 자동으로 중단되는 것을 원하지 않습니다.
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; }
}
모범 사례
여러 에이전트 워크플로를 구축한 후 배운 내용은 다음과 같습니다.- 상담원 지시에 집중하세요 — 각 상담원은 한 가지 일을 잘 수행해야 합니다. 스위스 군용 칼 요원을 만들려고 하지 마세요.
- 컨텍스트 제한 - 전체 대화 기록을 모든 상담원에게 전달하지 마세요. 각 단계에 필요한 것만 제공하십시오.
- 수정 제한 설정 — 품질 루프는 훌륭하지만 주의하지 않으면 영원히 실행될 수 있습니다. 항상 최대 반복 횟수 제한을 유지하세요.
- 모든 것을 기록 — 에이전트 출력은 예측할 수 없습니다. 디버깅을 위해 모든 단계의 입력 및 출력을 기록합니다.
- 병렬 실행을 현명하게 사용하세요 — 속도는 빨라지지만 API 속도 제한과 토큰 비용을 주의하세요.
- 더 작은 모델로 먼저 테스트 — 프로덕션을 위해 GPT-4o로 전환하기 전에 GPT-3.5로 워크플로 논리를 개발하고 테스트하세요.
결론
에이전트 워크플로를 사용하면 각 에이전트가 전문가인 복잡한 다단계 AI 파이프라인을 구축할 수 있습니다. 순차 워크플로로 시작하고, 단계가 독립적인 병렬 실행을 추가하고, 품질 게이트에 조건부 분기를 사용하세요. 패턴은 구성 가능합니다. 일단 익숙해지면 매우 정교한 자동화를 구축할 수 있습니다.
즐거운 코딩하세요!