20장. 복합 요청 분해 처리
실습문제와 해답
Planner의 계획을 직접 찍어 보고, 문의의 성격에 따라 어느 구조에 맡길지 골라 봅니다. 먼저 스스로 해 본 뒤 해답을 펼쳐 보세요. 해답은 새 파일로 만들어 실행합니다.
문제 1 — 같은 문의를 19장의 라우터에 넣어 보기
「왜 복합 요청 분해가 필요한가」 절에서 본 결과를 직접 확인합니다. 새 파일 ex20_router_q6.py를 만들어, 20장의 문의를 19장의 그래프에 넣고 경로와 답변을 출력하세요.
- 힌트:
from lesson19_router import graph로 19장의 그래프를 가져옵니다. - 답변을 150자에서 자르지 말고 전부 출력합니다.
해답 보기
from lesson19_router import graph
q = ("지난주에 산 운동화가 작아서 반품하고 싶은데 배송비 얼마인지, "
"그리고 검정 270 재고 있으면 그걸로 교환 가능한지 알려주세요.")
result = graph.invoke({"question": q, "intent": "", "answer": "", "log": []})
print(f"고객: {q}")
print(f" 경로: {' → '.join(result['log'])}")
print(f"하루: {result['answer']}")
$ python ex20_router_q6.py
실행 결과 (문장은 실행할 때마다 달라집니다)
고객: 지난주에 산 운동화가 작아서 반품하고 싶은데 배송비 얼마인지, 그리고 검정 270 재고 있으면 그걸로 교환 가능한지 알려주세요.
경로: 분류 → 환불교환 → 정책 RAG 노드 처리
하루: 단순 변심 교환의 왕복 배송비는 6,000원이며 고객이 부담합니다(근거: 제4조 ②). 다만, 원하는 옵션의 재고가 없는 경우 교환은 불가하며, 반품 후 환불로 처리됩니다(근거: 제4조 ③). 재고 유무의 경우 제공된 정책 발췌 내용만으로는 확인 불가합니다.
해설 — 경로는 환불교환 → 정책 RAG 노드입니다. 4장의 분류기에는 "환불/반품/교환 관련이면 주문 이야기가 섞여 있어도 환불교환"이라는 규칙이 있었습니다. 분류기는 규칙대로 했고, 라우터는 표대로 했습니다. 정책 노드는 정책 문서에 있는 배송비와 교환 조건을 조항과 함께 답했습니다.
20장의 실행 결과와 나란히 놓습니다.
| 19장 라우터 | 20장 Planner-Worker | |
|---|---|---|
| 본 곳 | 정책 문서 | 주문 데이터, 정책 문서, 상품 데이터 |
| 주문 | — | 쿠션 운동화(화이트/250) |
| 배송비 | 교환 6,000원 | 교환 6,000원, 반품 3,000원 |
| 재고 | 정책 문서로는 확인 불가 | 옵션별 재고는 확인 필요 |
| LLM 호출 | 2번 | 8번 안팎 |
두 결과 모두 자기가 본 곳에 있는 내용만 답했습니다. 차이는 본 곳의 수입니다. 한 가지를 묻는 문의에는 호출 두 번의 라우터가 알맞고, 여러 가지를 묻는 문의에는 작업마다 알맞은 곳을 보는 Planner-Worker가 알맞습니다.
문제 2 — 계획만 찍어 보고, 하나를 끝까지 실행하기
Worker를 실행하지 않고 계획만 세워 보면, Planner가 문의를 어떻게 읽는지 빠르고 싸게 확인할 수 있습니다.
(1) 새 파일 ex20_plan_only.py를 만들어 아래 네 문의의 계획을 출력하세요.
- "단순 변심 반품이면 배송비 얼마예요?"
- "제 최근 주문 지금 어디까지 왔어요?"
- "저 VIP인데요, 단순 변심 반품해도 배송비 내야 해요?"
- "이어폰 배송 어디쯤인지 보고, 취소하면 환불 언제 되는지 알려주세요."
(2) 새 파일 ex20_vip.py를 만들어 3번 문의를 plan_and_execute로 끝까지 실행하세요.
- 힌트:
from lesson20_planner_worker import make_plan,from lesson20_planner_worker import plan_and_execute - 실행하기 전에 문의마다 작업이 몇 개 나올지 적어 보세요.
- 3번 문의의 고객은 스스로 VIP라고 말했습니다. 계획은 이 말을 어떻게 다룰까요?
해답 보기
(1) 계획만 찍기
from lesson20_planner_worker import make_plan
QUESTIONS = [
"단순 변심 반품이면 배송비 얼마예요?",
"제 최근 주문 지금 어디까지 왔어요?",
"저 VIP인데요, 단순 변심 반품해도 배송비 내야 해요?",
"이어폰 배송 어디쯤인지 보고, 취소하면 환불 언제 되는지 알려주세요.",
]
for q in QUESTIONS:
plan = make_plan(q) # 계획만 세운다. Worker 는 실행하지 않는다
print(f"\n문의: {q}")
print(f" 작업 수: {len(plan.tasks)}")
for i, t in enumerate(plan.tasks, 1):
print(f" {i}. [{t.task_type.value}] {t.instruction}")
$ python ex20_plan_only.py
실행 결과 (문장은 실행할 때마다 달라집니다)
문의: 단순 변심 반품이면 배송비 얼마예요?
작업 수: 1
1. [정책확인] 단순 변심 반품 시 발생하는 배송비 규정을 확인한다.
문의: 제 최근 주문 지금 어디까지 왔어요?
작업 수: 1
1. [주문조회] 고객의 최근 주문 번호와 현재 배송 상태를 확인한다.
문의: 저 VIP인데요, 단순 변심 반품해도 배송비 내야 해요?
작업 수: 2
1. [주문조회] 고객의 VIP 등급 및 주문 정보를 확인한다.
2. [정책확인] VIP 회원의 단순 변심 반품 시 배송비 면제 정책이 있는지 확인한다.
문의: 이어폰 배송 어디쯤인지 보고, 취소하면 환불 언제 되는지 알려주세요.
작업 수: 2
1. [주문조회] 고객의 이어폰 주문 배송 상태를 확인한다.
2. [정책확인] 현재 배송 상태에 따른 주문 취소 시 환불 소요 기간을 확인한다.
세 번 실행했을 때 작업 수와 작업 유형은 세 번 모두 같았습니다(1, 1, 2, 2). 달라진 것은 지시문의 문장뿐입니다.
- 묻는 것이 하나인 문의는 작업도 하나입니다. 1번과 2번이 그렇습니다. "문의를 최소 작업으로 분해한다. 불필요한 작업을 만들지 않는다"는 Planner의 지시가 지켜졌습니다.
- 3번 문의의 첫 작업은 등급 확인입니다. 고객이 "저 VIP인데요"라고 말했지만, 계획은 그 말을 그대로 믿지 않고 등급을 먼저 조회하도록 세워졌습니다. 그다음에 VIP의 반품 배송비 규정을 찾습니다.
- 4번 문의는 두 곳을 봅니다. 배송 상태는 주문 데이터에, 환불 기간은 정책 문서에 있습니다.
이 확인에 든 것은 문의마다 LLM 호출 한 번입니다. 계획이 데이터로 먼저 나오기 때문에, Worker를 한 번도 실행하지 않고 Planner만 따로 볼 수 있습니다.
(2) 3번 문의를 끝까지 실행하기
from lesson20_planner_worker import plan_and_execute
q = "저 VIP인데요, 단순 변심 반품해도 배송비 내야 해요?"
print(f"고객: {q}\n")
print(f"\n하루: {plan_and_execute(q)}")
$ python ex20_vip.py
실행 결과 (문장은 실행할 때마다 달라집니다)
고객: 저 VIP인데요, 단순 변심 반품해도 배송비 내야 해요?
[Plan]
1. [주문조회] 고객의 VIP 등급 및 주문 내역을 확인한다.
2. [정책확인] VIP 회원의 단순 변심 반품 시 배송비 면제 혜택이 있는지 확인한다.
[Worker 1 완료] - 고객 멤버십 등급: VIP (보유 포인트: 12,500P)
- 최근 주문 내역: 총 8건 (HR20260...
[Worker 2 완료] 발췌 내용에 따른 확인 결과는 다음과 같습니다.
* **[표 2] 등급별 혜택 요약** 및 **제2조 (등...
하루: 안녕하세요, 하루마켓 상담원 '하루'입니다.
고객님은 VIP 등급이시며, 단순 변심 반품 배송비 면제 혜택은 월 1회 제공되어 자동 적용됩니다. 다만, 월 2회째부터는 일반 기준인 편도 3,000원이 적용되는 점 참고 부탁드립니다. 반품을 원하시면 채팅에서 바로 접수를 도와드리겠습니다.
해설 — 답변의 두 사실이 각각 어디서 왔는지 봅니다.
| 답변의 내용 | 근거가 된 보고 | 그 Worker가 본 곳 |
|---|---|---|
| "고객님은 VIP 등급이시며" | Worker 1 | 고객 데이터 (get_my_membership) |
| "월 1회 면제, 2회째부터 편도 3,000원" | Worker 2 | 멤버십 정책 문서 |
Worker 1의 보고에 등급 VIP와 보유 포인트 12,500이 들어 있습니다. 고객이 말한 등급이 아니라 조회한 등급입니다. worker_order에 get_my_membership 도구를 함께 준 덕분에 "등급 확인"이라는 작업을 맡을 수 있었습니다. Worker 2는 그 뒤에 VIP의 혜택 조항을 찾았고, 취합이 둘을 한 답으로 묶었습니다.
세 번 실행했을 때 세 번 모두 계획은 주문조회(등급 확인) → 정책확인이었고, 답변에는 VIP 등급과 월 1회 면제가 들어 있었습니다.
문제 3 — 어느 구조에 맡길 것인가
다음 세 문의를 각각 19장의 라우터, 20장의 Planner-Worker, 9장의 ReAct 루프 가운데 어디에 맡기는 것이 좋을지 고르고 이유를 쓰세요. (코드를 실행하지 않고 글로 답합니다.)
- "하루클럽 골드 등급 혜택이 뭐예요?"
- "지난달에 산 이불커버 교환 배송비가 얼마인지, 화이트 재고가 있는지, 그리고 제 포인트로 배송비를 낼 수 있는지 알려 주세요."
- "주문한 게 안 와요. 뭐가 문제인지 좀 봐 주세요."
해답 보기
| 문의 | 맡길 곳 | 이유 |
|---|---|---|
| 1. 골드 등급 혜택 | 라우터 | 묻는 것이 하나이고 답이 정책 문서 한곳에 있습니다. 분류 1번, 답변 1번이면 됩니다. 계획과 취합을 거칠 이유가 없습니다 |
| 2. 교환 배송비 + 재고 + 포인트 | Planner-Worker | 문의만 읽어도 할 일을 셀 수 있습니다(주문·포인트 확인, 정책 확인, 재고 확인). 볼 곳이 여럿이라 작업으로 나눕니다 |
| 3. 주문한 게 안 와요 | ReAct 루프 | 무엇을 조회해야 할지 해 봐야 압니다. 주문 목록을 보고, 배송 중인 것이 있으면 상태를 보고, 결제만 된 주문이면 다른 안내를 해야 합니다. 앞 결과에 따라 다음 행동이 달라지므로 계획을 미리 세우기 어렵습니다 |
고르는 기준은 두 가지 질문입니다.
- 묻는 것이 하나인가, 여럿인가? 하나면 라우터로 충분합니다.
- 할 일을 미리 셀 수 있는가? 셀 수 있으면 Planner-Worker, 해 봐야 알면 ReAct 루프입니다.
2번의 "포인트로 배송비를 낼 수 있는지"는 두 가지를 봐야 답할 수 있습니다. 고객의 포인트 잔액은 주문조회 Worker가 get_my_membership으로 확인하고, 포인트 사용 규정은 정책확인 Worker가 정책 문서에서 찾습니다. 문제 2에서 등급을 조회한 것과 같은 방식입니다.
세 구조는 하나만 골라 쓰는 것이 아닙니다. 한 서비스 안에서도 문의의 성격에 따라 섞어 씁니다.