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

멀티 에이전트

이 페이지에 묻기

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

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

MULTI · AGENT

멀티 에이전트

생성 수정

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

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

19차시 · 조건 분기와 라우팅

지도 위치 : 3일차 · README 4장의 ⑤ 협업 층 | 이전: 18차시 그래프 기반 워크플로우 | 다음: 20차시 복합 요청 분해 처리 키워드 : 조건부 엣지, 라우터, 분기 기준

이 차시를 다 읽으면

  1. 조건부 엣지와 라우터를 유와 종차로 정의하고, 일반 엣지와 구별할 수 있다.
  2. 규칙 라우터와 LLM 라우터를 "누가 흐름을 정하는가" 축 위에 놓고 언제 무엇을 쓸지 고를 수 있다.
  3. 분기 기준을 겹치지 않게 · 빠짐없이 설계하고, 기타 · 낮은 확신 · 사람에게 넘기기 경로를 둘 수 있다.
  4. 되돌아가는 엣지(재시도 · 명확화 질문)를 그리고, 무한 루프를 코드로 막을 수 있다.
  5. 라우팅 정확도를 평가셋과 혼동표로 측정하고, 어느 분기가 틀리는지 읽을 수 있다.

0. 이 차시의 판별 축

#축왼쪽 (0)오른쪽 (1)이 축으로 갈리는 것큰 축 (README 5장)
1누가 흐름을 정하는가 ⭐코드 — 규칙 라우터모델 — LLM 라우터갈림길에서 길을 고르는 주체5.2 중심 축
2구문론 ↔ 의미론키워드 · 형식이 맞는지뜻이 무엇인지라우터가 무엇을 보고 고르나⑧ 구문론 ↔ 의미론
3결정론 ↔ 확률론같은 입력 → 같은 길같은 입력 → 다른 길 가능라우팅을 재현할 수 있는가⑧의 파생
4선형 ↔ 그래프앞으로만 감되돌아가는 엣지재시도 · 명확화가 가능한가⑤의 파생
5부탁 ↔ 강제"너무 많이 반복하지 마"반복 횟수를 State에 세고 코드로 끊음무한 루프가 실제로 막히는가②의 파생

이 차시가 답하는 질문은 "요청 유형에 따라 다른 길로 보내려면 갈림길을 어떻게 설계하나?" 입니다. 앞 차시(18차시)에서 오피스봇을 그래프로 다시 그렸지만 아직 한 줄이었습니다. 앞에서 배운 의도 분류기(4차시)와 다중 도구 라우팅(7차시)에서 이미 "유형을 보고 갈라 보내기"를 했는데, 그때는 갈림길이 if 문 안에 숨어 있었습니다. 이번에는 그 갈림길을 그래프 그림 위에 꺼내 놓고 , 되돌아가는 길과 멈추는 조건까지 함께 설계합니다.


1. 조건부 엣지와 라우터

1.1 정의

조건부 엣지 — [엣지]인데, [ 노드가 끝난 뒤 State를 보고 여러 후보 중 다음 노드를 하나 고른다 ]는 점에서 늘 같은 노드로 가는 일반 엣지와 구별된다.

라우터 — [함수]인데, [ State를 받아 다음에 갈 길의 이름을 돌려준다 ]는 점에서 State를 바꾸는 노드와 구별된다. 조건부 엣지는 라우터가 돌려준 이름을 보고 길을 정합니다.

일반 엣지조건부 엣지
다음 노드늘 하나로 고정라우터의 답에 따라 여럿 중 하나
코드add_edge("A", "B")add_conditional_edges("A", router, {...})
State를 바꾸나—❌ 라우터는 읽기만 함

⚠️ 라우터는 State를 바꾸지 않습니다. "의도를 분류해 State에 쓰는 일"은 노드 (classify)가 하고, 라우터는 State에 쓰인 intent를 읽어서 길 이름만 돌려줍니다. 둘을 나누면 분류 결과가 State와 실행 추적에 남아 나중에 검수할 수 있습니다(축 ③ — 값과 동작의 분리, 18차시).

1.2 규칙 라우터 ↔ LLM 라우터

규칙 라우터LLM 라우터
고르는 방법키워드 · 정규식 · 필드 값앞에서 배운 의도 분류기(4차시)를 노드로 두고 그 결과를 읽음
축 1 (누가 정하나)코드모델 (판단) + 코드 (갈 수 있는 길 목록)
축 2구문 — "휴가"라는 글자가 있는가의미 — "쉬는 날 며칠 남았지?"도 휴가조회로
축 3결정론 — 늘 같은 길확률론 — 경계 사례에서 흔들릴 수 있음
비용 · 지연거의 없음LLM 호출 한 번 (가벼운 모델 권장)
맞는 경우형식이 정해진 입력 (버튼 선택, 사번 형식)사람이 자유롭게 쓴 문장

⭐ 둘은 경쟁이 아니라 차례로 씁니다. 규칙으로 확실히 갈리는 것(화면의 "장비 신청" 버튼을 눌렀다)은 규칙 라우터가 먼저 보내고, 나머지 자유 문장만 LLM 분류를 거칩니다. 확실한 것까지 모델에게 묻는 것은 비용과 흔들림만 늘립니다.

누가 흐름을 정하는가 에서 LLM 라우터는 가운데쯤에 있습니다. 모델이 "어느 길"을 판단하지만, 갈 수 있는 길의 목록과 모양은 코드가 미리 그어 두었습니다. 모델이 목록에 없는 길을 만들 수는 없습니다. 이 점이 매번 모델이 행동을 정하는 에이전트 루프(9차시)와 다릅니다.

1.3 반례 — 이건 라우팅이 아닙니다

대상왜 아닌가
에이전트가 도구를 고르는 것 (7 · 9차시)모델이 도구 목록에서 고르지만, 그래프의 다음 노드 를 정하는 것이 아님
한 노드 안의 if 문갈라지지만 그래프 그림에 보이지 않음. 검수 · 추적이 안 됨
모든 노드를 병렬로 다 돌리고 하나만 쓰기고르는 것이 아니라 다 실행함. 비용이 노드 수만큼 듦 (20차시의 병렬은 결과를 모두 쓰는 경우)

💡 비유 (기억 보조용) — 라우터는 고속도로 갈림목의 표지판 담당 입니다. 차(State)를 고치지 않고 번호판만 보고 어느 출구로 갈지 가리킵니다.


2. 분기 기준 설계

2.1 네 가지 원칙

원칙뜻어기면
겹치지 않게한 요청이 두 길에 동시에 맞지 않도록"노트북 반납 규정"이 IT장애와 규정질문 사이에서 흔들림
빠짐없이어느 길에도 안 맞는 요청이 갈 기타 길분류기가 억지로 가장 가까운 길을 고름
낮은 확신 경로모델이 확신하지 못하면 되묻기 로틀린 길로 자신 있게 감
사람에게 넘기기범위 밖 · 민감 · 반복 실패는 담당자에게봇이 할 수 없는 일을 붙잡고 헛돎

⭐ "겹치지 않게"는 프롬프트 문장보다 경계 사례 목록 으로 정합니다. "노트북 반납 절차 는 규정질문, 노트북이 안 켜짐 은 IT장애"처럼 헷갈리는 쌍을 적어 두고, 그 쌍을 분류기 프롬프트의 예시와 평가셋에 함께 넣습니다.

2.2 오피스봇 분기 기준표

길들어오는 조건예가는 노드
휴가조회본인의 휴가 잔여 · 사용 내역"연차 몇 개 남았어?"hr_tools
규정질문사내 규정 · 절차를 묻는 것"노트북 반납 절차는?"search_policy
장비신청장비를 새로 받으려는 것"모니터 하나 더 받고 싶어요"equipment (23차시 승인)
IT장애지금 무언가가 고장 · 안 됨"VPN이 안 붙어요"it_tools
기타위 넷 어디에도 안 맞음"점심 뭐 먹지?"fallback (범위 안내)
낮은 확신confidence == "low""그거 어떻게 해요?"clarify (되묻기)
사람에게연봉 · 징계 · 신고 등 범위 밖, 되묻기 2회 실패"팀장 신고하고 싶어요"escalate (23차시)

3. add_conditional_edges 코드

from pydantic import BaseModel
from typing import Literal

class Intent(BaseModel):                          # 4차시 분류기 스키마 재사용
    intent: Literal["휴가조회", "규정질문", "장비신청", "IT장애", "기타"]
    confidence: Literal["high", "low"]
    reason: str

def classify(state: State) -> dict:               # 노드: 분류해서 State에 씀
    r = client.messages.parse(model="claude-haiku-5-5", max_tokens=1024,
                              system=CLASSIFY_PROMPT,
                              messages=[{"role": "user", "content": state["question"]}],
                              output_format=Intent)
    out = r.parsed_output
    return {"intent": out.intent, "confidence": out.confidence}

def route(state: State) -> str:                   # 라우터: 읽기만 하고 길 이름을 돌려줌
    if state.get("button") == "장비신청":          # 규칙 먼저
        return "장비신청"
    if state["confidence"] == "low":
        return "낮은확신"
    return state["intent"]

g.add_conditional_edges("classify", route, {
    "휴가조회": "hr_tools", "규정질문": "search_policy", "장비신청": "equipment",
    "IT장애": "it_tools", "기타": "fallback", "낮은확신": "clarify",
})

🔍 읽는 포인트

  • Literal[...] — 분류 결과가 이 다섯 값 중 하나 로만 나오도록 형식을 강제합니다(4차시). 형식은 보장되지만 맞게 분류했는지는 보장되지 않습니다 (축 ⑧) → 5절의 평가.
  • route — 규칙이 먼저 , 그다음 확신도, 마지막에 모델의 분류. 1.2의 "차례로 쓰기"입니다. 2.2의 "사람에게" 길은 짧게 줄이려고 뺐습니다. 실제로는 범위 밖 키워드 규칙과 4.2의 반복 실패에서 "사람에게"를 돌려주고 대응표에 escalate를 더합니다.
  • 마지막 dict — 라우터가 돌려줄 수 있는 이름 → 실제 노드 대응표. 이 표가 곧 2.2의 분기 기준표입니다. 표에 없는 이름이 나오면 실행 중 오류가 나므로, Literal 값과 이 표가 빠짐없이 맞는지 확인합니다.
  • model="claude-haiku-5-5" — 라우팅은 대량 · 경량 작업이라 작은 모델로 충분한지 먼저 평가해 봅니다. 가격은 바뀌므로 공식 가격 페이지를 확인하세요.

4. 되돌아가는 엣지와 무한 루프 방지

4.1 되돌아가는 두 경우

경우길예
재시도검색 결과가 비었으면 질문을 고쳐 다시 검색search_policy → rewrite_query → search_policy (15차시 쿼리 재작성)
명확화 질문확신이 낮으면 사용자에게 되묻고 답을 받아 다시 분류classify → clarify → (사용자 답) → classify

그래프에서는 이것이 이미 지나온 노드로 가는 엣지 일 뿐입니다. 체인으로는 그리기 어려웠던 모양입니다(18차시 축 1).

4.2 ⚠️ 무한 루프 — 코드로 끊는다

되돌아가는 엣지가 생기는 순간 영원히 돌 수 있습니다. 앞에서 배운 루프 가드레일(10차시)에서 최대 반복 횟수를 코드로 걸었던 것과 같은 원리를 그래프에도 겁니다.

class State(TypedDict):
    question: str
    sources: list[str]
    retries: int                                   # 몇 번 되돌아갔나

def after_search(state: State) -> str:
    if state["sources"]:
        return "answer"
    if state["retries"] >= 2:                      # 상한: 코드로 강제
        return "escalate"                          # 사람에게 넘김
    return "rewrite_query"

def rewrite_query(state: State) -> dict:
    return {"question": rewrite(state["question"]), "retries": state["retries"] + 1}

result = app.invoke(inputs, config={"recursion_limit": 25})   # 그래프 전체의 안전장치

🔍 읽는 포인트

  • retries — 되돌아간 횟수를 State에 셉니다. 라우터는 이 숫자를 보고 상한에 닿으면 다른 길로 보냅니다.
  • return "escalate" — 상한에 닿았을 때 그냥 실패로 끝내지 않고 사람에게 넘기는 길로 보냅니다(2.1 원칙 4).
  • recursion_limit — LangGraph가 그래프 전체의 실행 단계 수에 거는 상한입니다. 넘으면 오류로 멈춥니다. retries가 설계상의 상한 이라면 이것은 최후의 안전망 입니다. 기본값과 이름은 버전에 따라 다를 수 있으니 문서를 확인하세요.
  • 명확화 질문은 사용자 답을 기다려야 하므로 그래프가 멈췄다 재개 해야 합니다. 그 방법은 23차시(인터럽트)에서 다룹니다.

5. 라우팅 정확도 평가

분류가 틀리면 그 뒤의 모든 노드가 엉뚱한 일을 합니다. 앞에서 배운 평가셋(4 · 7차시)을 라우팅에 그대로 씁니다. 다만 이번에는 "정답률 한 숫자"보다 어느 길이 어느 길과 헷갈리는지 를 보는 혼동표 가 쓸모 있습니다.

혼동표 — [평가 결과표]인데, [ 정답 길(행)과 라우터가 고른 길(열)을 교차해 칸마다 건수를 센다 ]는 점에서 전체 정답률 하나만 보는 요약과 구별된다.

정답 \ 라우터휴가조회규정질문장비신청IT장애기타낮은확신
휴가조회
규정질문
장비신청
IT장애
기타

(빈칸 양식입니다. 실측 후 채우세요.)

읽는 법뜻할 일
대각선 칸맞게 보낸 건수—
대각선 밖의 큰 칸두 길이 겹친다2.1 경계 사례를 프롬프트 · 기준표에 추가
"낮은확신" 열이 큼너무 자주 되물음사용자가 귀찮아함. 기준 · 예시 보강
장비신청 행이 다른 길로 샘쓰기 작업이 빠짐가장 위험. 우선 수정

⚠️ 평가셋에는 쉬운 질문만이 아니라 경계 사례 (노트북 반납 vs 노트북 고장)를 일부러 넣으세요. 쉬운 질문만으로 측정한 정답률은 높게 나와도 실제와 다릅니다.


6. 오피스봇 분기도

[classify] ─┬─ 휴가조회 → [hr_tools] ──────────────────────────→ [answer] → END
            ├─ 규정질문 → [search_policy] ─ 결과 있음 ─────────→ [answer]
            │                  ↑      └─ 결과 없음 → [rewrite_query]
            │                  └──────────────────────┘  (2회까지, 그 뒤엔 → [escalate])
            ├─ IT장애 ──→ [it_tools] ──────────────────────────→ [answer]
            ├─ 장비신청 → [equipment] → 23차시 승인 게이트
            ├─ 기타 ────→ [fallback] (오피스봇이 다루는 범위 안내)
            └─ 낮은확신 → [clarify] → (사용자 답) → [classify]로 되돌아감
                                       (되묻기 2회 실패 → [escalate])

앞으로 다시 만납니다 — 20차시에서는 한 문장에 의도가 여럿 들어 있는 요청("휴가 잔여 확인하고 노트북 반납 절차도")을 다룹니다. 이 차시의 라우터는 길을 하나만 고르므로, 그런 요청은 분해가 필요합니다. 22차시의 관리 에이전트는 라우터 자리에 모델이 매번 다음 담당을 고르는 노드를 둡니다.


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

지시

18차시에서 만든 오피스봇 그래프에 조건 분기를 붙여줘.
참고 공식 문서: LangGraph의 add_conditional_edges, recursion_limit 설정 항목, Claude API 구조화 출력(messages.parse).
  설치된 langgraph 버전 기준으로 작성할 것.
구조: classify 노드(4차시 Intent 스키마) → route 라우터 → 2.2 분기 기준표의 7개 길.
제약:
- 라우터는 State를 바꾸지 말 것. 규칙(button 필드) → confidence → intent 순서.
- search_policy가 빈 결과면 rewrite_query로 되돌아가되 재작성 2회 뒤에도 비면 escalate.
- recursion_limit을 설정하고 그 값을 보고할 것.
테스트: 평가셋 40문항(길별 5개 이상 + 경계 사례 10개 포함)을 돌려 혼동표를 채울 것.
  추가로 ① 검색 결과가 늘 비는 질문 ② "그거요" 같은 모호한 질문을 넣어 되돌아가는 길을 시험할 것.
보고 형식: 혼동표 + [질문 | 정답 길 | 라우터의 길 | confidence | reason] 오답 목록 표 + ①②의 stream 출력 원문.
추측하지 말고 실행 출력 원문을 붙여.

받을 증거

  • 그래프 그림(draw_mermaid()) — 6절 분기도와 비교용
  • 채워진 혼동표 와 오답 목록(모델이 남긴 reason 포함)
  • ①에서 rewrite_query가 정확히 2번 돌고 escalate로 간 실행 기록

내가 직접 확인할 것

볼 것이걸로 아는 것
그래프 그림의 갈림길이 기준표의 7개 길과 일치 하는가설계대로 갈렸는가 (2.2)
혼동표 대각선 밖에서 가장 큰 칸어느 두 길이 겹치는가 → 기준 보강 (2.1)
장비신청 행에서 다른 길로 샌 건수쓰기 작업이 빠지는 위험 (5)
①의 실행 기록에서 반복이 멈췄는가무한 루프를 코드가 막는가 (4.2)
오답의 reason을 직접 읽기프롬프트 문제인지, 기준 자체가 모호한지

부록 A. 스스로 확인하기

#질문답이 있는 곳
1조건부 엣지와 라우터를 유와 종차로 정의하면?1.1
2라우터가 State를 바꾸지 않는 이유는?1.1
3규칙 라우터와 LLM 라우터는 각각 언제 쓰나?1.2
4LLM 라우터가 에이전트 루프와 다른 점은?1.2
5분기 기준의 네 원칙은?2.1
6add_conditional_edges의 마지막 dict는 무엇과 같아야 하나?3
7retries와 recursion_limit은 각각 무슨 역할인가?4.2
8혼동표가 정답률 하나보다 쓸모 있는 이유는?5

부록 B. 자주 하는 실수

실수바로잡기
라우터 안에서 LLM을 부르고 State도 고침⚠️ 분류는 노드에서, 라우터는 읽기만. 그래야 분류 결과가 추적에 남습니다
"기타" 길을 두지 않음❌ 모든 요청이 억지로 가장 가까운 길로 갑니다
버튼 선택처럼 확실한 것도 LLM에게 물음⚠️ 비용과 흔들림만 늘어납니다. 규칙을 먼저
되돌아가는 엣지에 횟수 상한이 없음❌ 무한 루프. State에 세고 코드로 끊으세요
상한에 닿으면 그냥 오류로 끝냄⭕ 사람에게 넘기는 길(escalate)로 보내세요
쉬운 질문만으로 정답률 측정❌ 경계 사례를 넣어야 실제와 가까워집니다
스키마가 맞으니 분류도 맞다고 봄❌ 형식(구문)과 내용(의미)은 다른 축입니다

부록 C. NotebookLM 복습 프롬프트

이 19차시 노트만 근거로 비전공자용 5문제를 내줘.
"규칙 라우터와 LLM 라우터를 누가 흐름을 정하는가 축에 놓기", "분기 기준 네 원칙", "무한 루프를 코드로 끊기", "혼동표 읽기"는 반드시 포함해줘.
내가 답한 뒤에 채점하고, 틀린 이유를 노트의 절 번호를 근거로 설명해줘.