콘텐츠로 이동

8. 오케스트레이션 패턴

“"8. 오케스트레이션 패턴"” 챕터 컨셉 일러스트

한 줄 정의. 오케스트레이션 패턴은 여러 LLM 호출이나 에이전트를 어떤 순서·어떤 권한으로 엮을지 정하는 제어 구조이며, 누가 다음 단계를 결정하느냐(코드냐 모델이냐)에 따라 통제력과 유연성이 갈린다.

대표 오케스트레이션 패턴 비교도

단일 에이전트 루프는 에이전트 지도에서 본 계획·행동·관찰 한 묶음으로 충분한 문제만 풉니다. 하지만 입력 유형이 갈리거나, 하위 작업을 미리 못 박을 수 없거나, 전문성이 다른 일을 나눠 맡겨야 하는 순간 “호출을 어떻게 엮을 것인가”가 별도의 설계 문제로 떠오릅니다. 오케스트레이션 패턴은 바로 그 배선을 다룹니다.

워크플로 대 에이전트: 모든 패턴이 놓이는 제어 축

섹션 제목: “워크플로 대 에이전트: 모든 패턴이 놓이는 제어 축”

Anthropic은 이런 구성을 두 부류로 나눕니다. 워크플로는 “LLM과 도구가 사전에 정의된 코드 경로(predefined code paths)로 오케스트레이션되는 시스템”이고, 에이전트는 “LLM이 자기 과정과 도구 사용을 동적으로 지휘하며 과업을 어떻게 완수할지에 대한 통제를 스스로 유지하는 시스템”입니다.1

이 한 줄짜리 정의 차이가 이 장의 모든 패턴을 가르는 축입니다. 코드가 흐름을 쥐면 비용·지연·동작이 예측 가능해지고, 모델에 흐름을 넘기면 못 박지 못한 작업까지 유연하게 처리하는 대신 조정과 오류 처리가 까다로워집니다.

OpenAI Agents SDK도 똑같이 둘로 가릅니다. 열린 과제는 LLM이 자율적으로 계획하게 두는 “LLM이 지휘(orchestrating via LLM)“하는 방식, 그리고 “속도·비용·성능 면에서 더 결정론적이고 예측 가능한” 코드가 지휘(orchestrating via code)하는 방식입니다.2

여기서 가장 자주 빠뜨리는 토대가 두 가지입니다. 첫째, 모든 패턴의 빌딩블록은 증강된 LLM(augmented LLM) 입니다.

검색(retrieval)·도구(tools)·메모리(memory)가 붙은 한 개의 모델이 기본 단위이고, 이 모델은 자기 검색 쿼리를 직접 만들고, 어떤 도구를 쓸지 고르고, 무엇을 기억으로 남길지 스스로 결정합니다.1 라우터든 수퍼바이저든 결국 이 증강 LLM 여러 개를 어떻게 배선하느냐의 문제입니다.

둘째, 설계 원칙은 “성과가 눈에 띄게 좋아질 때만 복잡도를 더한다(add complexity only when it demonstrably improves outcomes)” 입니다.1 단순한 프롬프트 한 번과 평가(eval)로 시작하고, 그 단순한 해법이 분명히 부족할 때 비로소 멀티스텝 패턴으로 올라가야 합니다.

멀티에이전트가 멋져 보여서 먼저 그리는 것이 가장 흔한 실패입니다.

파이프라인: 고정 순차 + 게이트

섹션 제목: “파이프라인: 고정 순차 + 게이트”

이 장에서 “파이프라인”이라 부르는 것은 Anthropic의 프롬프트 체이닝(prompt chaining) 에 해당합니다. 정의는 “각 LLM 호출이 직전 호출의 출력을 처리한다(each LLM call processes the output of the previous one)“이고, 단계 사이에 프로그래밍적 게이트(gate) 를 끼워 중간 산출이 기준을 만족하는지 검증할 수 있습니다.1

마케팅 카피를 만든 뒤 다른 언어로 번역하기, 문서 개요를 먼저 잡고 그 개요가 일정 기준을 통과하는지 검사한 다음 그 개요를 바탕으로 본문을 쓰기 같은 것이 전형입니다.

이 패턴이 잘 맞는 조건은 명확합니다. “과업이 고정된 하위 작업들로 쉽고 깔끔하게 분해될 수 있을 때(easily and cleanly decomposed into fixed subtasks)“입니다.1

분해가 깔끔하다는 것은 하위 작업의 개수와 순서가 입력과 무관하게 컴파일 시점에 정해진다는 뜻이고, 바로 이 점이 뒤에 나올 수퍼바이저와 갈리는 지점입니다. 트레이드오프는 지연(latency)을 정확도와 맞바꾼다는 것입니다.

호출을 여러 개로 쪼개면 각 호출이 단순해져 정확도가 오르지만, 직렬로 이어지므로 전체 응답 시간은 호출 수만큼 늘어납니다.

코드 지휘로 옮기면 두 형태가 됩니다. 한 에이전트의 출력을 다음 에이전트의 입력으로 변환해 잇는 단순 체이닝, 그리고 evaluator를 while 루프로 돌려 기준을 통과할 때까지 반복하는 형태입니다.2 후자가 위 caution에서 말한 evaluator-optimizer의 코드 구현입니다.

라우터는 “입력을 분류해 특화된 후속 작업으로 보내는(classifies an input and directs it to a specialized follow-up task)” 패턴입니다.1 효과는 두 가지로 정리됩니다 — 관심사의 분리(separation of concerns) 와 더 특화된 프롬프트 입니다.

모든 입력을 하나의 거대 프롬프트로 처리하려 하면 환불 안내와 기술 디버깅이 같은 지침을 공유하며 서로의 품질을 갉아먹지만, 라우터로 갈라두면 각 경로가 자기 일에만 맞춘 프롬프트와 모델을 쓸 수 있습니다.

적용 조건은 “별도로 처리하는 편이 나은 뚜렷한 카테고리들이 있고, 분류를 정확하게 해낼 수 있는 복잡한 과업”입니다.1 두 조건이 모두 필요합니다. 카테고리 경계가 흐릿하거나 분류기가 자주 틀리면 라우터는 오히려 오류를 증폭합니다 — 분류 오류가 곧 경로 오류이기 때문입니다.

구체적으로는 두 결의 분기가 흔합니다. 하나는 과업 종류로 가르는 것입니다.

고객 문의를 일반(general)·환불(refunds)·기술지원(technical support)으로 보냅니다. 다른 하나는 난이도로 가르는 것입니다.

쉬운 질문은 값싸고 빠른 Claude Haiku 4.5로, 어려운 질문은 더 강한 Claude Sonnet 4.5로 보냅니다.1 후자가 라우팅의 비용 절감 효과를 가장 직접적으로 보여줍니다 — 단순 질의의 대다수를 저비용 모델이 흡수하면 평균 호출 단가가 크게 내려갑니다.

“값싼 모델/강한 모델”이라는 추상화 뒤에는 이런 실제 모델 등급과 단가 차이가 깔려 있습니다.

수퍼바이저: 위임하고 종합한다

섹션 제목: “수퍼바이저: 위임하고 종합한다”

수퍼바이저(Anthropic 용어로는 orchestrator-workers)는 “중앙 LLM이 작업을 동적으로 쪼개 워커 LLM들에게 위임하고 그 결과를 종합하는(dynamically breaks down tasks, delegates them to worker LLMs, and synthesizes their results)” 구조입니다.1 라우터·병렬화와 결정적으로 다른 한 줄은 이것입니다 — “하위 작업이 미리 정의되지 않고, 오케스트레이터가 구체적 입력에 따라 결정한다(subtasks aren’t pre-defined, but determined by the orchestrator based on the specific input).”1 다시 말해 무엇을 몇 개로 나눌지가 입력마다 달라 코드로 못 박을 수 없을 때 쓰는 패턴입니다.

여러 파일에 걸친 코드 수정(어느 파일을 몇 개 고칠지는 변경 요청을 봐야 안다)이나 출처를 넘나드는 리서치가 전형입니다.

LangGraph의 langgraph-supervisor는 이를 도구 기반 핸드오프(tool-based handoff)로 구현합니다. 중앙 수퍼바이저가 모든 통신과 위임을 통제하고, create_supervisor로 구성합니다.

from langgraph_supervisor import create_supervisor
from langgraph.prebuilt import create_react_agent
supervisor = create_supervisor(
agents=[research_agent, math_agent],
model=model,
prompt="당신은 두 워커를 관리하는 수퍼바이저입니다...",
output_mode="last_message", # 또는 "full_history"
add_handoff_back_messages=True,
supervisor_name="supervisor",
).compile()

핵심 파라미터는 agents, model, prompt, 그리고 자주 틀리는 output_mode입니다.4

대가는 분명합니다. 수퍼바이저가 모든 통신을 거치므로 그 한 지점이 병목이자 단일 실패점 입니다. 수퍼바이저가 다른 수퍼바이저를 거느리는 다단계 팀(계층 구조)으로 확장할 수 있지만, 그러면 중앙을 거치는 통신 경로가 더 길어집니다.4 CrewAI의 계층 프로세스는 이 수퍼바이저를 역할 기반으로 구현한 형태에 가깝습니다(아래 크루 절 참조).

그래프 워크플로: 흐름을 명시적으로 그린다

섹션 제목: “그래프 워크플로: 흐름을 명시적으로 그린다”

그래프 워크플로는 노드(작업)와 엣지(다음으로 갈 곳)로 제어 흐름을 명시합니다. 원칙은 단순합니다 — 노드가 일을 하고, 엣지가 다음에 무엇을 할지 결정합니다.

조건부 엣지로 분기하고, 순환(cycle)을 허용해 재시도·반복 루프를 그립니다. LangChain은 이런 흐름을 “결정론적 로직과 에이전트적 행동을 섞어” LangGraph로 직접 짜는 길을 열어 둡니다.3

라우터·수퍼바이저·파이프라인은 모두 이 노드+조건 엣지의 특수한 배치로 환원되므로, 그래프는 다른 패턴들의 공통 토대로 볼 수 있습니다.

Command 프리미티브: 상태 갱신과 분기를 한 번에

섹션 제목: “Command 프리미티브: 상태 갱신과 분기를 한 번에”

Command는 상태 갱신(update)과 다음 노드 지정(goto)을 한 도구 반환으로 묶습니다. 멀티에이전트 핸드오프에서는 graph=Command.PARENT로 부모 그래프로 점프해 다른 에이전트 노드로 넘깁니다.5

from langgraph.types import Command
from langchain_core.messages import AIMessage, ToolMessage
def handoff_to_sales(state) -> Command:
return Command(
goto="sales_agent",
update={
"active_agent": "sales_agent",
"messages": [
AIMessage(content="", tool_calls=[{"name": "transfer_to_sales", "args": {}, "id": "1"}]),
ToolMessage(content="Transferred to sales", tool_call_id="1"),
],
},
graph=Command.PARENT,
)

Send API: 개수를 모르는 동적 병렬

섹션 제목: “Send API: 개수를 모르는 동적 병렬”

Send는 분기 개수를 컴파일 시점에 모를 때를 위한 프리미티브입니다 — “개수를 사전에 알 수 없을 때 Send가 빛난다(Send shines when the count is not known ahead of time).”6 라우팅 함수 안에서 항목마다 Send 객체를 만들어 리스트로 반환하면, LangGraph가 각 인스턴스에 서로 다른 state를 보내 한 superstep에서 동시에 실행합니다. 모든 분기가 끝나야 다음으로 진행합니다(map-reduce의 map 단계).

from langgraph.types import Send
def fan_out(state):
# 항목 수가 입력마다 다름 → 엣지를 미리 못 박음
return [Send("process_item", {"item": x}) for x in state["items"]]

직접 그래프를 다루면 분기·재시도·중단 지점을 손에 쥐는 대신, 흐름을 일일이 정의하는 설계 부담을 집니다. 에이전트 사이의 메시지가 실제로 어떤 규약을 타고 오가는지는 9. 프로토콜 계층에서 자세히 살펴봅니다.

크루(CrewAI)는 역할·목표·배경을 가진 에이전트들이 협업하는 단위입니다. 작업을 굴리는 방식은 두 프로세스로 갈립니다.

순차(sequential) 는 “작업 실행이 작업 목록의 사전 정의된 순서를 따르며, 한 작업의 출력이 다음 작업의 컨텍스트가 된다(the output of one task serving as context for the next)“는 방식입니다.7 여기서 세밀한 제어점은 Task의 context 파라미터입니다 — 어떤 선행 작업의 출력을 다음 작업에 넘길지 골라 지정할 수 있어, 직전 출력만이 아니라 특정 선행 결과들을 선택적으로 전달합니다.

계층(hierarchical) 은 매니저 에이전트가 계획·위임·검토·완료 검증을 수행하는 방식입니다. 핵심은 “작업이 미리 배정되지 않고, 매니저가 에이전트들의 역량에 따라 작업을 배분한다(Tasks are not pre-assigned; the manager allocates tasks to agents based on their capabilities)“는 점 — 실행 시점의 동적 배정 입니다.7

이 점에서 계층 프로세스는 수퍼바이저를 역할 기반으로 구현한 형태입니다.

CrewAI는 자율성에 무게를 둔 Crews 와 이벤트 기반으로 실행을 정밀하게 통제하는 Flows 를 구분합니다. Crews는 에이전트가 스스로 조직(자기조직)하고, Flows는 이벤트 기반으로 흐름을 정확히 통제합니다.8 Flows의 제어는 데코레이터로 짭니다.

  • @start() — 진입점. 조건이 만족된 것들이 종종 병렬로 실행됩니다.
  • @listen() — 선행 단계가 출력을 emit하면 트리거됩니다.
  • @router() — 분기를 만듭니다.
  • or_() / and_() — 여러 트리거를 결합하는 결합자입니다.

상태는 구조화(Pydantic BaseModel) 또는 비구조화(dict)로 들고 다닙니다.8 진가는 둘을 결합할 때 나옵니다 — Flow가 여러 Crew를 더 큰 워크플로의 단계로 오케스트레이션 하면서 자율성(Crew)과 정밀 통제(Flow)를 저울질하는 식입니다.8

스웜은 “에이전트들이 각자의 전문성에 따라 서로에게 제어를 동적으로 넘기는(dynamically hand off control to one another based on their specializations)” 멀티에이전트 아키텍처입니다.9 수퍼바이저와의 결정적 차이는 중앙 지휘자가 없다는 것입니다.

라우팅 판단이 한가운데가 아니라 개별 에이전트의 핸드오프 도구에 내장되어, 동료들이 대등하게 책임을 인계합니다.

상태 기억이 스웜의 핵심 메커니즘입니다. active_agent state 변수가 마지막으로 활성화된 에이전트 를 추적하고, “시스템이 어떤 에이전트가 마지막으로 활성이었는지 기억해, 이후 상호작용에서 그 에이전트부터 대화를 재개합니다(resumes with that agent).”9 즉 사용자가 결제 전문가와 대화를 나누다 멈췄다면, 다음 턴은 다시 결제 전문가에서 이어집니다.

from langgraph_swarm import create_swarm, create_handoff_tool
workflow = create_swarm(
[alice, bob],
default_active_agent="Alice",
)
app = workflow.compile(
checkpointer=checkpointer, # 단기 메모리: 대화 상태 유지에 필수
store=store, # 장기 메모리(선택)
)

create_handoff_tool은 기본적으로 전체 메시지 이력 과 핸드오프 성공을 알리는 도구 메시지를 다음 에이전트에 전달합니다. 도구명·설명, LLM이 채울 인자, 전달할 데이터를 커스터마이즈할 수 있습니다.9

OpenAI Agents SDK의 핸드오프 도 같은 발상입니다. “분류(triage) 에이전트가 대화를 전문가에게 라우팅하면, 그 전문가가 그 턴의 나머지 동안 활성 에이전트가 됩니다.” 즉 넘겨받은 전문가가 사용자에게 직접 응답 합니다.2

이것이 매니저 패턴과 갈리는 지점입니다 — 매니저 패턴(as_tool)에서는 전문가를 도구로 호출하고 매니저가 최종 답을 소유·결합 하지만, 핸드오프에서는 제어가 전문가에게 완전히 넘어갑니다.

대가는 추적성입니다. 핸드오프 판단이 한가운데가 아니라 개별 에이전트의 도구에 흩어져 있으므로, 수퍼바이저처럼 한 지점만 들여다봐서는 전체 동작을 따라가기 어렵습니다 — 제어가 어디서 누구에게 넘어갔는지가 여러 에이전트에 분산됩니다.

위 여섯 패턴 사이에는 Anthropic 목록에서 함께 다뤄지는 두 패턴이 더 있습니다. 라우터·파이프라인을 고를 때 이 둘과의 경계를 알아야 잘못 분류하지 않습니다.

병렬화(parallelization) 는 두 결입니다. 섹셔닝(sectioning) 은 독립적인 하위 작업을 동시에 굴리는 것이고, 투표(voting) 는 같은 작업을 여러 번 굴려 다양한 출력을 얻는 것입니다.

적용 조건은 “나뉜 하위 작업을 병렬화해 속도를 얻을 수 있을 때, 또는 더 높은 신뢰를 위해 여러 관점·여러 시도가 필요할 때”입니다.1 가드레일이 좋은 예입니다 — 한 모델이 사용자에게 응답하는 동안 다른 모델이 그 입력의 부적절성을 병렬로 스크리닝합니다.

코드 취약점 리뷰도 마찬가지로, 여러 모델이 같은 코드를 따로 검토하고 투표 임계값으로 판정합니다.

evaluator-optimizer 는 한 LLM이 생성하고 다른 LLM이 피드백을 주는 루프입니다. 적용 조건은 “명확한 평가 기준이 있고, 반복 개선이 측정 가능한 가치를 줄 때”입니다.1 앞서 짚었듯 본문 예제의 ‘게이트 → 되돌림’ 루프는 실은 이 패턴에 더 가깝습니다.

OpenAI Agents SDK는 오케스트레이션을 LLM이 지휘 하느냐 코드가 지휘 하느냐로 가릅니다.2 LLM 지휘는 핸드오프와 도구로 열린 문제를 풀고, 코드 지휘는 흐름을 박아 속도·비용·동작을 결정론적으로 잡습니다.

코드 지휘의 네 가지 대표 기법은 ① 구조화 출력(structured output)으로 라우팅 결정을 받기, ② 한 에이전트의 출력을 다음 입력으로 잇는 체이닝, ③ evaluator를 while 루프로 돌리기, ④ asyncio.gather로 병렬 실행입니다.2 LLM 지휘를 쓸 때의 굿프랙티스는 좋은 프롬프트, 모니터링·반복, 명확한 도구 명세, 범용이 아닌 전문화된 에이전트, 그리고 eval에 대한 투자입니다.2

아래 표는 여섯 패턴을 이 축 위에 정리한 것이며, 자세한 결정 근거는 본문 각 절에 있습니다. 프레임워크마다 이 패턴들을 어떤 이름과 기본값으로 제공하는지는 12. 프레임워크 비교에서 나란히 견줍니다.

패턴흐름 결정 주체잘 맞는 상황주된 대가
라우터분류기(모델)입력 유형이 또렷이 갈릴 때분류 오류가 곧 경로 오류
수퍼바이저중앙 LLM하위 작업을 미리 못 박을 때조정 비용·중앙 병목
그래프코드(명시 엣지)분기·재시도를 손에 쥐고 싶을 때흐름 정의 부담
크루매니저 또는 순서역할 분담이 뚜렷할 때자율성과 예측성 사이
스웜개별 에이전트대등한 전문가 간 인계추적·디버깅 난도
파이프라인코드(고정 순서)단계가 정해지고 검증이 필요할 때속도를 정확도와 맞바꿈

리서치 보조 시스템을 단계별로 패턴에 대입해 보겠습니다.

  1. 라우터 — 들어온 질문을 “단순 사실 확인 / 심층 조사 / 코드 작성”으로 분류합니다. 단순 질의는 값싼 모델(예: Haiku 4.5)로 바로 답하고, 분류가 정확해야 경로가 어긋나지 않습니다.1
  2. 수퍼바이저 — 심층 조사로 분류된 건은 조정자 LLM이 “검색 → 출처 평가 → 초안”으로 하위 작업을 그때그때 쪼개 워커에게 위임하고 결과를 종합합니다. 하위 작업 수가 질문마다 다르므로 코드로 못 박을 수 없습니다 — 이것이 라우터가 아니라 수퍼바이저를 쓰는 이유입니다.1
  3. 파이프라인 + evaluator — 종합된 초안은 “사실 검증 → 윤문 → 출력”의 고정 순서를 탑니다. 검증 단계에서 미검증 주장이 걸리면 이전 단계로 되돌리는데, 이 되돌림 루프는 단순 게이트가 아니라 evaluator-optimizer입니다(명확한 검증 기준 + 반복 개선).1

여기서 라우터는 분류 정확도가 관건이고, 수퍼바이저는 위임·종합 로직이 핵심이며, 파이프라인은 검증 단계가 품질을 지킵니다. 한 시스템 안에서도 구간마다 다른 패턴이 맞물린다는 점이 요지입니다. 권한을 떼어 다른 에이전트에 위임하는 구체적 방식은 7. 서브에이전트에서 따로 다룹니다.

  • 단순 해법부터 측정 했는가 — 프롬프트 한 번 + 도구 + eval로 안 풀릴 때만 멀티스텝으로 올렸는가(“성과가 분명히 좋아질 때만 복잡도 추가”).1
  • 이 구간의 흐름을 코드가 정할지 모델이 정할지 먼저 결정했는가(통제 대 유연의 축).2
  • 하위 작업을 미리 못 박을 수 있으면 파이프라인/그래프를, 실행 시점에야 정해지면 수퍼바이저/스웜을 골랐는가.1
  • 게이트(한 번 검사)와 evaluator-optimizer(반복 루프)를 혼동하지 않았는가.1
  • 수퍼바이저의 중앙 병목, 스웜의 디버깅 난도, 자율 에이전트의 오류 누적 등 선택한 패턴의 주된 대가 를 감수할 수 있는지 확인했는가.1
  1. Anthropic — Building effective agents 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

  2. OpenAI Agents SDK — Orchestrating multiple agents 2 3 4 5 6 7

  3. LangChain — Multi-agent 2

  4. langgraph-supervisor-py — GitHub 2 3

  5. LangChain — Multi-agent handoffs 2

  6. LangGraph Map-Reduce: Send API 동적 병렬 2

  7. CrewAI — Processes 2 3

  8. CrewAI — Flows 2 3

  9. langgraph-swarm-py — GitHub 2 3 4