실무 Multi-Agent 오케스트레이션 4장 · 의도 분류기 - 구조화 출력 4 / 7 ← 이전목차다음 → TechLead Cro

4장. 의도 분류기 - 구조화 출력

분류 라벨 설계 — 무엇으로 나눌지는 사람이 정한다

한 줄 요약

스키마는 도구일 뿐입니다. 라벨을 몇 개로, 어떤 기준으로 나눌지는 모델이 아니라 개발자가 정하는 업무 설계입니다. 라벨이 잘못 나뉘어 있으면 모델이 아무리 정확해도 뒤의 처리가 어긋납니다.


1. 일곱 개의 라벨

1장에서 문의 로그 40건을 여섯 유형으로 나눴습니다. 분류기의 라벨은 그 여섯에 "기타" 를 더한 일곱 개입니다.

class Intent(str, enum.Enum):
    PRODUCT = "제품문의"
    ORDER = "주문배송조회"
    REFUND = "환불교환"
    MEMBERSHIP = "멤버십적립금"
    ACCOUNT = "계정결제"
    HUMAN = "상담원연결"
    OTHER = "기타"
라벨 이런 문의 이 라벨이 붙으면 할 일
제품문의 "무드등 밝기 조절이 되나요?" 상품 데이터를 조회한다
주문배송조회 "주문한 거 어디쯤이에요?" 주문 데이터를 조회한다
환불교환 "반품 배송비 누가 내요?" 정책 문서를 찾는다
멤버십적립금 "골드 등급은 어떻게 돼요?" 멤버십 정책 문서를 찾는다
계정결제 "카드 결제가 실패해요" 안내하거나 상담원에게 넘긴다
상담원연결 "사람 바꿔 주세요" 상담원에게 넘긴다
기타 "오늘 날씨 좋네요" 처리하지 않고 정중히 안내한다

오른쪽 열이 중요합니다. 라벨은 분류가 목적이 아니라, 라벨마다 다른 처리를 하는 것이 목적입니다.


2. 좋은 라벨 체계의 세 조건

겹치지 않는다

한 문의가 두 라벨에 걸치면 모델은 그때그때 다르게 고릅니다. 그런데 실제 문의는 자주 걸칩니다. "운동화 아직 안 왔는데 환불되나요?"는 배송 이야기이면서 환불 이야기입니다.

이런 경계 사례를 어느 쪽으로 보낼지는 모델의 재량에 맡기지 않고 규칙으로 정해 시스템 프롬프트에 적습니다.

CLASSIFY_SYSTEM = """당신은 하루마켓 고객 문의 분류기입니다.
고객 문의를 읽고 지정된 JSON 스키마로만 답합니다.
- 사람을 직접 요구하면 상담원연결
- 환불/반품/교환 관련이면 주문·재고 이야기가 섞여 있어도 환불교환
- 하루클럽 등급·하루포인트·적립금 관련이면 멤버십적립금
- 주문 상태·배송 위치·배송지 변경 관련이면 주문배송조회
- 상품의 사양·재고·재입고 관련이면 제품문의
- 로그인·회원정보·결제 수단·결제 오류 관련이면 계정결제
- 판단이 어려우면 기타"""

규칙의 둘째 줄이 경계 규칙입니다. 왜 환불 쪽으로 보낼까요. 환불 안내가 틀렸을 때 잃는 것(돈과 신뢰)이 배송 조회가 틀렸을 때보다 크기 때문입니다. 겹치면 더 중요한 쪽이 가져가게 정했습니다. 규칙에 이런 이유가 있어야 나중에 고칠 때 기준이 섭니다.

나머지 줄은 "이런 문의면 이 라벨" 을 라벨마다 한 줄씩 적은 것입니다. 상담원연결, 환불교환, 멤버십적립금, 주문배송조회, 제품문의, 계정결제 여섯 라벨에 한 줄씩 있고, 어디에도 맞지 않으면 기타입니다. 라벨마다 기준을 적어 두어야 "재입고 알림은 어디서 신청하나요?" 같은 문의도 망설임 없이 제품문의로 갑니다.

빠지는 것이 없다

어떤 입력이 와도 갈 곳이 있어야 합니다. "기타" 가 그 자리입니다.

"기타"가 없으면 모델은 "오늘 날씨 좋네요"도 여섯 라벨 중 하나에 억지로 넣어야 합니다. 스키마가 여섯 값만 허용하기 때문입니다. 모르겠다고 말할 자리를 주어야 나머지 라벨이 깨끗해집니다. 3장의 "모르면 모른다고 한다"가 분류기에서는 이렇게 구현됩니다.

라벨마다 처리가 다르다

처리가 완전히 같은 두 라벨은 합치는 것을 따져 보고, 한 라벨 안에서 처리가 갈리면 나누는 것을 따져 봅니다. 라벨 수가 늘수록 경계 사례도 늘어납니다.


3. 긴급도 — 기준을 적어 준다

urgency도 같은 방식입니다. "낮음 / 보통 / 높음"을 모델의 느낌에 맡기면 같은 문의에도 값이 오락가락합니다. 그래서 세 값이 각각 어떤 문의인지를 시스템 프롬프트에 적어 줍니다.

[긴급도 기준]
- 높음: 배송 지연·결제 오류처럼 문제가 이미 생겼거나, 재촉하거나, 사람을 요구하는 문의
- 보통: 환불·교환·변경처럼 처리를 요청하는 문의
- 낮음: 상품 정보·이용 방법·정책을 단순히 묻는 문의

그리고 스키마의 urgency 칸 설명은 이 기준을 가리키게 합니다.

urgency: Urgency = Field(description="처리 긴급도. 아래 [긴급도 기준]을 따른다")
값 기준 예
높음 문제가 이미 생겼다 / 재촉한다 / 사람을 요구한다 "카드 결제가 세 번이나 실패했어요. 빨리 해결해 주세요."
보통 처리를 요청한다 "사이즈가 안 맞아서 교환하고 싶어요."
낮음 단순히 묻는다 "무드등 밝기 조절이 되나요?"

판정 기준은 적어 준 만큼 일정해집니다. 라벨의 경계 규칙과 같은 원리입니다. 이 기준을 적은 뒤 상담 문의 로그 40건을 두 번 분류해 보면, 두 번 모두 높음 10건·보통 14건·낮음 16건으로 똑같이 나옵니다.


4. temperature는 0.0

3장에서 정리했듯, 분류는 답이 정해진 일입니다. 다양한 답이 필요하지 않으므로 temperature=1.0으로 둡니다.

Gemini 3 계열에서는 temperature 기본값(1.0)을 사용합니다. 온도만으로 결과의 일관성을 보장할 수 없으므로 출력 형식과 규칙을 명시하고 실제 응답을 확인합니다.


5. 누가 무엇을 정했나

정한 것 누가
라벨을 일곱 개로 나눈다 개발자
겹치면 환불교환으로 보낸다 개발자
문제가 이미 생겼으면 높음, 처리 요청은 보통, 단순 문의는 낮음이다 개발자
이 문의는 셋 중 어느 긴급도인가 모델
이 문의는 일곱 중 어느 것인가 모델
문장 어디에 주문번호가 있는가 모델
한 줄로 어떻게 요약할 것인가 모델

분류기가 틀렸을 때 이 표의 어느 줄이 틀렸는지부터 봅니다. 위 세 줄이 틀렸으면 규칙을 고치고, 아래 세 줄이 틀렸으면 설명과 프롬프트를 다듬습니다.


핵심 정리

  • 라벨은 라벨마다 다른 처리를 하기 위해 나눕니다.
  • 두 라벨에 걸치는 경계 사례는 규칙으로 정해 시스템 프롬프트에 적습니다.
  • "기타" 는 모르겠다고 말할 자리입니다. 이것이 있어야 나머지 라벨이 깨끗합니다.
  • 판정은 규칙과 설명에 적어 준 만큼 일정해집니다. 라벨마다 기준을 한 줄씩 적습니다.
  • 라벨과 규칙은 개발자가, 문의 하나하나의 판정은 모델이 합니다.
← 이전 절Pydantic으로 스키마 쓰기 — 클래스가 곧 명세다다음 절 →따라하기 — 의도 분류기 만들기
오명운 · macro@prag-ai.com