본문으로 건너뛰기
페이지 목차
이 페이지에서 검색

멀티 에이전트

이 페이지에 묻기

멀티 에이전트 · 현재 차시와 목차 본문에서만 찾아 답합니다. 답을 지어내지 않고 해당하는 대목을 그대로 보여 주며, 여기에 없으면 없다고 알려 드립니다.

생성형 AI가 아니라 이 페이지 본문을 찾아 그대로 보여 주는 방식입니다. 외부로 나가는 요청이 없습니다.

MULTI · AGENT

멀티 에이전트

생성 수정

도구 실행·검색·기억·그래프 흐름·전문 에이전트의 협업을 구성하고 권한·평가·사람의 확인으로 통제합니다.

26개 문서 · 3일차 · 21 / 26

20차시 · 복합 요청 분해 처리

지도 위치 : 3일차 · README 4장의 ⑤ 협업 층 | 이전: 19차시 조건 분기와 라우팅 | 다음: 21차시 전문 에이전트 구현 키워드 : Planner-Worker, 작업 의존성, 결과 취합

이 차시를 다 읽으면

  1. 복합 요청이 왜 한 번의 분류 · 라우팅(19차시)으로 처리되지 않는지 설명할 수 있다.
  2. Planner-Worker를 유와 종차로 정의하고, 에이전트 루프 · 라우팅과 구별할 수 있다.
  3. 계획을 구조화 출력 (작업 목록 + 의존 관계)으로 받고, 그 계획을 사람이 읽어 검수할 수 있다.
  4. 작업 의존성으로 순서대로 할 일과 동시에 할 일 을 가르고(축 ⑥), 병렬 실행의 비용 · 호출 한도를 말할 수 있다.
  5. 워커 결과를 취합할 때 충돌과 누락 을 확인하고, 계획이 실패하면 재계획하는 길을 설계할 수 있다.

0. 이 차시의 판별 축

#축왼쪽 (0)오른쪽 (1)이 축으로 갈리는 것큰 축 (README 5장)
1하나 ↔ 여럿 ⭐요청을 한 덩어리로 처리작업 여럿으로 나눠 처리분해할 것인가⑤ 하나 ↔ 여럿
2동기 ↔ 비동기 ⭐작업을 하나씩 차례로독립 작업을 동시에기다리는 동안 다른 작업을 하는가⑥ 동기 ↔ 비동기
3누가 흐름을 정하는가실행 순서 · 병렬 여부는 코드작업 목록은 모델(Planner)계획과 실행을 누가 맡나5.2 중심 축
4한 건의 비용 ↔ 전체 비용호출 한 번의 토큰계획 + 워커 N개 + 취합의 토큰분해의 대가⑤의 파생
5구문론 ↔ 의미론계획이 스키마에 맞는가계획이 요청을 빠짐없이 덮는가계획 검수에서 무엇을 보나⑧ 구문론 ↔ 의미론

이 차시가 답하는 질문은 "한 문장에 일이 여러 개 들어 있을 때, 어떻게 나누고 · 어떤 순서로 돌리고 · 다시 합치나?" 입니다. 앞 차시(19차시)의 라우터는 요청 하나에 길을 하나 골랐습니다. 하지만 직원은 "다음 주 월요일 휴가 쓰려는데 잔여 확인하고, 노트북 반납 절차도 알려줘"처럼 한 번에 여러 일을 묻습니다. 앞에서 배운 도구 호출(5 · 7차시)에서 모델은 한 응답에 도구 요청을 여러 개 담을 수 있었습니다(병렬 도구 호출 — 결과를 모아 한 번에 돌려보냄). 이번에는 그 생각을 작업 단위 로 키웁니다.


1. 복합 요청과 Planner-Worker

1.1 복합 요청 — 라우터 하나로는 안 되는 이유

"다음 주 월요일 휴가 쓰려는데 잔여 확인하고, 노트북 반납 절차도 알려줘."

들어 있는 일의도 (19차시 기준)필요한 것
① 휴가 잔여 확인휴가조회휴가 조회 도구 (6차시)
② 다음 주 월요일 휴가 신청 가능 여부휴가조회 + 규정질문①의 결과 + 휴가 규정
③ 노트북 반납 절차규정질문규정 문서 검색 (13~15차시)

라우터는 이 중 하나 만 고릅니다. 그러면 나머지 둘은 빠지거나, 분류기가 확신을 잃고 "낮은 확신"으로 보냅니다. 필요한 것은 먼저 나누고, 나눈 것을 각각 처리하고, 다시 합치는 구조입니다.

1.2 정의

Planner-Worker — [멀티스텝 처리 패턴]인데, [ 계획 담당(Planner)이 요청을 작업 목록으로 먼저 나누고, 실행 담당(Worker)들이 각 작업을 처리한 뒤 결과를 취합한다 ]는 점에서 한 단계씩 다음 행동을 정하는 에이전트 루프와 구별된다.

에이전트 루프 (9차시)라우팅 (19차시)Planner-Worker
계획없음 — 매 반복마다 다음 행동 판단없음 — 길 하나 고름실행 전에 전체 작업 목록
처리 단위도구 호출 한 번씩요청 하나 → 노드 하나작업 여러 개 → 워커 여러 개
병렬한 응답 안의 병렬 도구 호출 정도없음독립 작업을 동시에
사람이 미리 볼 수 있나어려움 (끝나야 앎)길 이름 하나계획을 실행 전에 읽을 수 있음

1.3 반례 — 이건 Planner-Worker가 아닙니다

대상왜 아닌가
계획 없이 같은 질문을 워커 셋에게 던지기나누지 않았음. 같은 일을 세 번 할 뿐
코드가 고정한 세 단계 (항상 분류 → 검색 → 답변)요청마다 작업 목록이 바뀌지 않음. 18차시의 고정 그래프
관리 에이전트가 매번 다음 담당을 고르는 것미리 계획하지 않고 매번 판단 함. 22차시 Supervisor

💡 비유 (기억 보조용) — Planner는 작업 지시서를 쓰는 반장 , Worker는 지시서 한 줄씩 맡는 작업자 입니다. 반장은 직접 일하지 않고, 작업자는 지시서의 자기 줄만 봅니다.


2. 계획을 구조화 출력으로

2.1 계획 스키마

계획을 자유로운 글로 받으면 코드가 실행할 수 없습니다. 앞에서 배운 구조화 출력(4차시)으로 작업 목록과 의존 관계 를 받습니다.

from pydantic import BaseModel
from typing import Literal

class Task(BaseModel):
    id: str                                         # "t1", "t2" ...
    worker: Literal["hr", "policy", "it"]           # 맡을 워커 (허용 목록)
    instruction: str                                # 워커에게 줄 한 줄 지시
    depends_on: list[str]                           # 먼저 끝나야 하는 작업 id

class Plan(BaseModel):
    tasks: list[Task]
    covers: list[str]                               # 원래 요청의 어느 부분을 덮는지

r = client.messages.parse(model="claude-opus-5-5", max_tokens=2048,
                          system=PLANNER_PROMPT,
                          messages=[{"role": "user", "content": request}],
                          output_format=Plan)
plan = r.parsed_output

🔍 읽는 포인트

  • worker: Literal[...] — Planner가 없는 워커를 지어내지 못하게 허용 목록으로 묶습니다. 형식으로 강제하는 것입니다(부탁 ↔ 강제).
  • depends_on — 이 작업 전에 끝나야 하는 작업. 3절의 실행 순서를 정하는 유일한 근거입니다.
  • covers — 원래 요청의 어느 구절을 덮는지 Planner가 적게 합니다. 사람이 빠진 일이 없는지 볼 때 씁니다(축 ⑧ — 형식이 맞아도 내용은 따로 봐야 함).
  • Planner에는 판단이 필요한 큰 모델을, 워커에는 작은 모델을 쓰는 조합이 흔합니다(21차시). 가격은 바뀌므로 공식 가격 페이지를 확인하세요.

2.2 이 요청의 계획 (예시)

idworkerinstructiondepends_on
t1hr요청자의 올해 휴가 잔여 조회—
t2policy휴가 신청 기한 · 절차 규정 검색—
t3hrt1 · t2 결과로 다음 주 월요일 휴가 가능 여부 판단t1, t2
t4policy노트북 반납 절차 규정 검색—

⚠️ t3은 판단만 하고 휴가를 실제로 신청하지 않습니다. 신청은 쓰기 작업이라 사람의 승인을 거쳐야 합니다(23차시). Planner가 쓰기 작업을 계획에 넣었다면 실행 단계에서 코드가 승인 게이트로 보냅니다.


3. 작업 의존성 — 순서와 병렬

3.1 의존 관계를 그림으로

작업 의존성 — [작업 사이의 관계]인데, [ 한 작업이 다른 작업의 결과를 입력으로 필요로 한다 ]는 점에서 서로 상관없는 독립 작업과 구별된다.

 t1 (휴가 잔여) ──┐
                  ├──→ t3 (월요일 휴가 가능?) ──┐
 t2 (휴가 규정) ──┘                             ├──→ [취합] → 답
 t4 (노트북 반납) ──────────────────────────────┘

이런 그림을 DAG (방향이 있고 되돌아가는 고리가 없는 그래프)라고 부릅니다. 직관은 두 줄이면 됩니다.

관계실행축 ⑥
앞 결과가 필요하면 (t3은 t1 · t2가 필요)순서대로동기 — 기다림
서로 상관없으면 (t1 · t2 · t4)동시에비동기 — 기다리는 동안 다른 작업

⚠️ 축 ⑥의 함정을 다시 짚습니다. 병렬로 돌린다고 작업 하나가 빨라지지 않습니다. t1이 3초 걸리면 병렬에서도 3초입니다. 셋을 차례로 하면 각자의 시간을 더한 만큼 걸리고, 동시에 하면 가장 긴 것 만큼 걸릴 뿐입니다.

⚠️ 계획에 고리(t1이 t3을 기다리고, t3이 t1을 기다림)가 있으면 영원히 시작하지 못합니다. 실행 전에 코드로 고리 여부를 검사 하고, 있으면 재계획(5절)으로 보냅니다.

3.2 워커 실행 — 병렬과 동시 호출 한도

import asyncio
client = anthropic.AsyncAnthropic()
limit = asyncio.Semaphore(3)                         # 동시에 최대 3개만

async def run_task(task, results):
    async with limit:
        context = {d: results[d] for d in task.depends_on}   # 앞 작업 결과만 넘김
        return await WORKERS[task.worker](task.instruction, context)

async def execute(plan):
    results, pending = {}, list(plan.tasks)
    while pending:
        ready = [t for t in pending if all(d in results for d in t.depends_on)]
        outs = await asyncio.gather(*(run_task(t, results) for t in ready))
        for t, out in zip(ready, outs):
            results[t.id] = out
        pending = [t for t in pending if t.id not in results]
    return results

🔍 읽는 포인트

  • ready = [...] — 앞 작업이 다 끝난 작업만 이번 차례에 고릅니다. 첫 차례에는 t1 · t2 · t4, 다음 차례에 t3.
  • asyncio.gather(...) — 고른 작업을 동시에 돌리고 모두 끝날 때까지 기다립니다.
  • Semaphore(3) — 동시에 나가는 호출 수의 상한 . API에는 분당 호출 · 토큰 한도가 있어 한꺼번에 많이 보내면 429(anthropic.RateLimitError)가 납니다. SDK가 몇 번 재시도하지만, 상한을 코드로 거는 것이 먼저입니다.
  • context = {d: ...} — 워커에게 필요한 앞 결과만 넘깁니다. 대화 전체를 넘기면 토큰이 늘고 워커가 남의 작업까지 하려 듭니다.
  • ⚠️ 이 루프에는 고리 검사가 빠져 있습니다. 고리가 있으면 ready가 비어 영원히 돕니다. 실제 코드에서는 ready가 비면 멈추고 재계획으로 보냅니다.

💡 LangGraph에서는 한 노드에서 여러 노드로 동시에 엣지를 내거나, Send로 워커를 작업 수만큼 띄우는 방식으로 같은 일을 합니다. 이때 워커들이 같은 State 필드에 결과를 쓰므로 리듀서 (18차시)가 필요합니다. 세부 API는 버전에 따라 다르니 공식 문서를 확인하세요.


4. 결과 취합

결과 취합 — [처리 단계]인데, [ 여러 워커의 결과를 모아 원래 요청 하나에 대한 답으로 합치며 충돌과 누락을 확인한다 ]는 점에서 결과를 이어 붙이기만 하는 것과 구별된다.

확인할 것예처리
누락t4가 실패해 노트북 반납 답이 없음답에 "확인하지 못함"을 명시 . 조용히 빼지 않음
충돌t1은 "잔여 2일", t3은 "잔여 3일 기준으로 가능"원천 결과(t1)를 우선, 충돌 사실을 기록
출처t2 · t4의 규정 조항워커가 낸 출처를 그대로 답에 유지 (14차시)
범위 밖워커가 묻지 않은 일까지 답함원래 요청의 covers와 대조해 잘라냄
FINAL_PROMPT = """아래 작업 결과만 근거로 직원의 원래 요청에 답하라.
- 실패한 작업은 '확인하지 못했습니다'라고 밝혀라. 추측으로 채우지 마라.
- 결과끼리 숫자가 다르면 조회 결과(hr)를 따르고, 다르다는 사실을 함께 적어라.
- 각 문장 끝에 근거 작업 id와 출처를 붙여라."""

🔍 읽는 포인트

  • "추측으로 채우지 마라" — 취합 단계는 빈칸을 그럴듯하게 메우려는 경향이 있습니다. 실패를 드러내는 쪽을 지시합니다.
  • "근거 작업 id" — 답의 어느 문장이 어느 워커에게서 왔는지 남기면, 틀린 답이 나왔을 때 어느 작업이 틀렸는지 바로 찾습니다(문장 3 — 근거와 측정).

5. 계획 실패 · 비용 · 검수

5.1 계획 실패와 재계획

실패알아채는 법대응
스키마에 안 맞는 계획parse 실패한 번 다시 요청
고리가 있는 계획코드의 고리 검사재계획
워커 하나 실패결과에 오류 표시 (is_error처럼)그 작업만 재시도 (읽기 작업만)
계획이 요청을 덜 덮음covers와 요청 대조, 사람 검수재계획
재계획도 실패재계획 횟수 상한사람에게 넘김 (19차시 escalate)

⚠️ 재계획도 되돌아가는 엣지입니다. 19차시와 같이 횟수를 State에 세고 코드로 끊습니다.

5.2 한 건의 비용 ↔ 전체 비용

분해는 공짜가 아닙니다. 한 번의 호출로 끝나던 요청이 계획 1회 + 워커 N회 + 취합 1회 가 됩니다.

단계모델호출 수입력 토큰출력 토큰비용
계획1
워커N
취합1
합계
비교: 분해 없이 한 번1

(빈칸 양식입니다. 비용 = 입력 토큰 × 입력 단가 + 출력 토큰 × 출력 단가. 단가는 공식 가격 페이지 기준으로, 토큰은 response.usage에서 실측해 채우세요.)

⭐ 판단 기준은 문장 1(가장 단순한 것부터)입니다. 요청의 대부분이 단일 의도라면, 복합 요청인지 먼저 가볍게 판별 하고 복합일 때만 Planner로 보냅니다. 모든 요청을 분해하면 단순한 질문까지 비싸고 느려집니다.

5.3 계획 검수 — 사람이 계획을 읽는다

Planner-Worker의 큰 장점은 실행 전에 계획이 글(표)로 남는다 는 것입니다. 에이전트 루프는 끝나야 무엇을 했는지 알지만, 계획은 미리 읽을 수 있습니다.

계획을 읽을 때 볼 것이상하면
요청의 모든 구절이 어느 작업에 덮였나누락
쓰기 작업(신청 · 변경)이 들어 있나승인 게이트 필요 (23차시)
워커 배정이 맞나 (노트북 반납을 it에게?)배정 기준 보강
작업 수가 지나치게 많나비용 폭주 신호

앞으로 다시 만납니다 — 21차시에서 워커를 전문 에이전트 (인사 · IT · 규정)로 키우고, 22차시에서 "미리 계획(이 차시) ↔ 매번 판단(Supervisor)"의 경계를 다룹니다. 23차시에서는 쓰기 작업이 든 계획을 사람이 승인하는 자리를 만듭니다.


6. 실습 — 맡기고 검수하기

지시

오피스봇에 복합 요청 처리를 붙여줘.
참고 공식 문서: Claude API 구조화 출력(messages.parse), 비동기 클라이언트(AsyncAnthropic), 레이트 리밋 · 에러 처리 문서.
  LangGraph로 구현한다면 설치된 버전의 병렬 실행(Send 등) 문서를 확인하고 버전을 보고할 것.
구조: 복합 여부 판별(가벼운 모델) → 복합이면 Planner(Plan 스키마) → 의존성 순서대로 워커 실행(독립 작업은 병렬, 동시 3개 상한) → 취합.
제약:
- 실행 전 계획의 고리 검사. 고리면 재계획, 재계획 2회 실패 시 escalate.
- 워커에게는 depends_on 결과만 전달. 쓰기 작업(신청)은 실행하지 말고 "승인 필요"로 표시만.
- 실패한 작업은 답에 "확인하지 못함"으로 드러낼 것.
테스트: ① "다음 주 월요일 휴가 쓰려는데 잔여 확인하고, 노트북 반납 절차도 알려줘" ② 단일 의도 질문 3개
  ③ t4 검색 도구를 일부러 실패시키기 ④ "휴가 신청해 줘"가 든 복합 요청
보고 형식: 시나리오마다 [계획 표(id | worker | instruction | depends_on)] + [실행 순서와 각 작업 시작 · 끝 시각]
  + [최종 답] + [usage 합계 토큰]. 5.2의 비용 표도 ①과 ②에 대해 채울 것.
추측하지 말고 실행 출력 원문을 붙여.

받을 증거

  • 시나리오별 계획 표 와 각 작업의 시작 · 끝 시각 로그
  • ③의 최종 답 원문 (실패가 드러났는가)
  • ①과 ②의 비용 표 (usage 실측)

내가 직접 확인할 것

볼 것이걸로 아는 것
①의 계획이 요청의 세 가지 일을 모두 덮는가계획의 의미 검수 (5.3)
t1 · t2 · t4의 시작 시각이 겹치고 , t3은 그 뒤에 시작하는가의존성대로 병렬 · 순서가 지켜졌나 (3.1)
②에서 Planner를 거치지 않았는가단순한 질문까지 분해하지 않는가 (5.2)
③의 답에 "확인하지 못함"이 있는가취합이 빈칸을 지어내지 않는가 (4)
④에서 신청이 실행되지 않고 "승인 필요"로 남았는가쓰기 작업이 코드로 막혔는가 (2.2)
분해 방식과 단일 호출의 비용 차이분해의 대가를 숫자로 알고 있는가 (5.2)

부록 A. 스스로 확인하기

#질문답이 있는 곳
1복합 요청을 라우터 하나로 처리하면 무엇이 빠지나?1.1
2Planner-Worker를 유와 종차로 정의하면?1.2
3Planner-Worker와 Supervisor(22차시)의 차이는?1.3
4계획 스키마에서 worker를 허용 목록으로 묶는 이유는?2.1
5순서대로 할 작업과 동시에 할 작업을 가르는 기준은?3.1
6병렬 실행이 작업 하나를 빨라지게 하지 않는 이유는?3.1
7동시 호출 상한을 거는 이유는?3.2
8취합에서 확인할 네 가지는?4
9모든 요청을 분해하면 안 되는 이유는?5.2

부록 B. 자주 하는 실수

실수바로잡기
계획을 자유로운 글로 받음❌ 코드가 실행할 수 없습니다. 스키마로 받으세요
"병렬이니까 빨라진다"⚠️ 작업 하나의 시간은 같습니다. 가장 긴 작업만큼 걸릴 뿐입니다
동시 호출 상한 없이 워커를 한꺼번에 띄움❌ 429 에러와 비용 급증. 상한을 코드로
워커에게 대화 전체를 넘김⚠️ 토큰이 늘고 워커가 남의 일까지 합니다. 필요한 앞 결과만
실패한 작업을 답에서 조용히 뺌❌ "확인하지 못함"을 드러내야 사용자가 압니다
단순한 질문도 Planner로 보냄⚠️ 비용 · 지연만 늘어납니다. 복합 여부를 먼저 가볍게 판별
계획에 든 신청 작업을 그대로 실행❌ 쓰기 작업은 승인 게이트로 (23차시)

부록 C. NotebookLM 복습 프롬프트

이 20차시 노트만 근거로 비전공자용 5문제를 내줘.
"Planner-Worker와 에이전트 루프 · 라우팅의 경계", "의존성으로 순서와 병렬 가르기", "병렬이 빨라지는 것이 아닌 이유", "결과 취합의 누락과 충돌"은 반드시 포함해줘.
내가 답한 뒤에 채점하고, 틀린 이유를 노트의 절 번호를 근거로 설명해줘.