본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2026. 2. 1. · 7 Views
Routing Workflow 완벽 가이드
AI 에이전트 시스템에서 사용자 요청을 적절한 처리기로 분류하고 전달하는 Routing Workflow 패턴을 학습합니다. 의도 분류부터 동적 라우팅, Fallback 처리까지 실전 예제와 함께 알아봅니다.
목차
1. Routing 패턴 이해
어느 날 김개발 씨는 고객 상담 챗봇을 개발하던 중 난감한 상황에 빠졌습니다. 사용자가 "환불해 주세요"라고 입력하면 환불 처리 모듈로, "배송 어디까지 왔어요?"라고 물으면 배송 조회 모듈로 보내야 하는데, 이걸 어떻게 자동으로 분류할 수 있을까요?
Routing Workflow는 들어온 요청을 분석하여 가장 적합한 처리기로 전달하는 패턴입니다. 마치 대형 병원의 접수처에서 환자의 증상을 듣고 적절한 진료과로 안내하는 것과 같습니다.
이 패턴을 이해하면 복잡한 AI 에이전트 시스템을 체계적으로 설계할 수 있습니다.
다음 코드를 살펴봅시다.
# Routing Workflow 기본 구조
class RoutingWorkflow:
def __init__(self):
# 각 의도에 맞는 핸들러를 등록합니다
self.handlers = {
"refund": RefundHandler(),
"shipping": ShippingHandler(),
"product": ProductHandler(),
}
self.classifier = IntentClassifier()
def route(self, user_input: str) -> Response:
# 1. 사용자 입력의 의도를 분류합니다
intent = self.classifier.classify(user_input)
# 2. 해당 의도에 맞는 핸들러를 찾습니다
handler = self.handlers.get(intent)
# 3. 핸들러가 요청을 처리합니다
return handler.process(user_input)
김개발 씨는 입사 6개월 차 주니어 개발자입니다. 최근 회사에서 AI 챗봇 프로젝트를 맡게 되었는데, 요구사항을 보자마자 머리가 복잡해졌습니다.
환불, 배송, 상품 문의, 회원 정보 수정까지 처리해야 할 업무가 너무 많았기 때문입니다. "이걸 하나의 if-else 문으로 처리하라고요?" 김개발 씨는 한숨을 쉬었습니다.
조건문이 수십 개가 되면 코드가 엉망이 될 게 뻔했습니다. 바로 그때 옆자리의 박시니어 씨가 말을 걸었습니다.
"Routing Workflow 패턴을 써보는 게 어때요? 이런 상황에 딱 맞거든요." 그렇다면 Routing Workflow란 정확히 무엇일까요?
쉽게 비유하자면, 대형 병원의 접수처를 떠올려 보세요. 환자가 "머리가 아파요"라고 하면 신경과로, "기침이 나요"라고 하면 호흡기내과로 안내합니다.
접수처 직원은 환자의 증상을 듣고 가장 적합한 진료과를 찾아주는 역할을 합니다. Routing Workflow도 마찬가지입니다.
사용자의 요청을 분석하고, 그에 맞는 처리기로 연결해 주는 것입니다. 이 패턴이 없던 시절에는 어땠을까요?
개발자들은 모든 분기 로직을 하나의 거대한 함수에 넣어야 했습니다. if문이 수십 개, 때로는 수백 개까지 늘어났습니다.
새로운 기능을 추가할 때마다 기존 코드를 건드려야 했고, 한 곳을 수정하면 다른 곳이 망가지기 일쑤였습니다. 바로 이런 문제를 해결하기 위해 Routing Workflow가 등장했습니다.
이 패턴을 사용하면 각 기능이 독립적인 핸들러로 분리됩니다. 새로운 기능을 추가할 때 기존 코드를 건드릴 필요가 없습니다.
그저 새 핸들러를 만들어 등록하면 끝입니다. 테스트도 개별 핸들러 단위로 할 수 있어 훨씬 편리합니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 handlers 딕셔너리를 보면 각 의도와 그에 맞는 처리기가 짝지어져 있습니다.
"refund"라는 키에는 RefundHandler가, "shipping"에는 ShippingHandler가 연결되어 있습니다. 이것이 바로 라우팅 테이블입니다.
route 메서드에서는 세 단계로 처리가 진행됩니다. 첫째, classifier가 사용자 입력을 분석해 의도를 파악합니다.
둘째, 그 의도에 맞는 핸들러를 딕셔너리에서 찾습니다. 셋째, 찾은 핸들러가 실제 업무를 처리합니다.
실제 현업에서는 이 패턴이 어디에 쓰일까요? 고객센터 챗봇이 대표적입니다.
쇼핑몰에서는 환불, 배송, 상품 문의, 회원 정보 등 다양한 요청이 들어옵니다. 각각을 담당하는 전문 모듈로 라우팅하면 더 정확하고 빠른 응답이 가능합니다.
대기업의 AI 상담 시스템 대부분이 이런 구조를 사용하고 있습니다. 박시니어 씨의 설명을 들은 김개발 씨는 무릎을 탁 쳤습니다.
"아, 그러니까 요청을 분류해서 적절한 곳으로 보내주는 교통정리 같은 거군요!" Routing Workflow를 이해하면 복잡한 AI 시스템도 깔끔하게 설계할 수 있습니다. 여러분의 프로젝트에도 적용해 보세요.
실전 팁
💡 - 핸들러는 단일 책임 원칙을 지켜 하나의 업무만 담당하게 설계하세요
- 라우팅 테이블은 설정 파일로 분리하면 유지보수가 편해집니다
2. 의도 분류
김개발 씨가 Routing Workflow의 개념은 이해했지만, 핵심적인 질문이 남았습니다. "사용자가 입력한 문장이 환불 요청인지, 배송 문의인지 어떻게 알 수 있죠?" 박시니어 씨가 웃으며 답했습니다.
"그게 바로 Intent Classification, 의도 분류야."
**의도 분류(Intent Classification)**는 사용자 입력 텍스트를 분석하여 미리 정의된 카테고리 중 하나로 분류하는 작업입니다. 마치 도서관 사서가 책 제목만 보고 어느 서가에 꽂을지 판단하는 것과 같습니다.
AI 에이전트에서 라우팅의 정확도는 이 의도 분류의 품질에 달려 있습니다.
다음 코드를 살펴봅시다.
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
class IntentClassifier:
def __init__(self):
self.llm = ChatOpenAI(model="gpt-4o-mini")
self.prompt = ChatPromptTemplate.from_template("""
사용자 메시지를 분석하여 의도를 분류하세요.
가능한 의도: refund, shipping, product, account, other
사용자 메시지: {message}
의도(한 단어만):""")
def classify(self, message: str) -> str:
# LLM을 사용해 의도를 분류합니다
chain = self.prompt | self.llm
result = chain.invoke({"message": message})
return result.content.strip().lower()
김개발 씨는 Routing Workflow의 구조는 이해했습니다. 하지만 한 가지 의문이 남았습니다.
사용자가 "환불해 주세요"라고 딱 떨어지게 말하면 좋겠지만, 현실은 그렇지 않았습니다. "주문한 거 취소하고 싶은데요", "돈 돌려받을 수 있나요?", "마음에 안 들어서 반품하려고요" 같은 다양한 표현을 모두 "환불"이라는 하나의 의도로 분류해야 했습니다.
박시니어 씨가 설명을 이어갔습니다. "예전에는 키워드 매칭을 썼어.
'환불'이라는 단어가 포함되면 환불 의도로 분류하는 식이었지." 하지만 이 방식에는 큰 한계가 있었습니다. "돈 돌려받고 싶어요"라는 문장에는 '환불'이라는 단어가 없습니다.
그렇다고 가능한 모든 표현을 키워드로 등록하는 것은 불가능에 가까웠습니다. 바로 이 문제를 해결한 것이 LLM 기반 의도 분류입니다.
LLM은 문장의 의미를 이해합니다. "돈 돌려받고 싶어요"와 "환불 처리해 주세요"가 같은 의도라는 것을 문맥으로 파악할 수 있습니다.
명시적인 키워드가 없어도 의미를 추론해내는 것입니다. 위의 코드를 자세히 살펴보겠습니다.
프롬프트 템플릿에서 중요한 부분은 "가능한 의도" 목록입니다. LLM에게 "이 중에서 하나를 골라"라고 명확히 지시하는 것입니다.
이렇게 하면 LLM이 엉뚱한 답을 내놓는 것을 방지할 수 있습니다. classify 메서드에서는 체인을 구성합니다.
프롬프트와 LLM을 파이프라인으로 연결하고, 사용자 메시지를 넣어 결과를 받습니다. 마지막에 strip()과 lower()로 정리하는 것은 LLM 출력의 불규칙성을 보정하기 위함입니다.
실제 서비스에서는 몇 가지 고려할 점이 있습니다. 첫째, 분류 정확도입니다.
환불 요청을 배송 문의로 잘못 분류하면 고객 불만이 커집니다. 프롬프트를 정교하게 다듬고, 각 의도에 대한 예시를 추가하면 정확도가 높아집니다.
둘째, 응답 속도입니다. LLM 호출은 시간이 걸립니다.
간단한 키워드 매칭으로 먼저 걸러내고, 애매한 경우에만 LLM을 사용하는 하이브리드 방식도 많이 쓰입니다. 셋째, 비용입니다.
API 호출마다 비용이 발생합니다. 자주 들어오는 패턴은 캐싱하거나, 작은 모델을 파인튜닝해서 사용하는 방법도 있습니다.
김개발 씨가 고개를 끄덕였습니다. "그러니까 LLM이 사서 역할을 하는 거네요.
책 제목만 보고도 어느 서가에 넣을지 아는 것처럼요." 의도 분류의 품질이 전체 시스템의 성능을 좌우합니다. 프롬프트 설계에 충분한 시간을 투자하세요.
실전 팁
💡 - 의도 목록은 명확하고 상호 배타적으로 설계하세요
- Few-shot 예시를 프롬프트에 추가하면 정확도가 크게 향상됩니다
- 애매한 케이스는 "other"로 분류하고 별도 처리하는 것이 안전합니다
3. 동적 라우팅 전략
며칠 후 김개발 씨는 새로운 과제를 받았습니다. 단순히 의도만으로 라우팅하는 것이 아니라, 사용자 등급, 시간대, 시스템 부하 등 다양한 조건을 고려해야 했습니다.
"VIP 고객은 전담 상담원에게 연결하고, 야간에는 자동 응답만 해야 해요."
동적 라우팅은 요청 시점의 다양한 조건을 종합적으로 고려하여 최적의 핸들러를 선택하는 전략입니다. 마치 내비게이션이 실시간 교통 상황을 반영해 경로를 추천하는 것과 같습니다.
정적인 규칙을 넘어 상황에 맞는 유연한 처리가 가능해집니다.
다음 코드를 살펴봅시다.
from dataclasses import dataclass
from datetime import datetime
from typing import Optional
@dataclass
class RoutingContext:
user_id: str
user_tier: str # "vip", "premium", "standard"
timestamp: datetime
system_load: float # 0.0 ~ 1.0
class DynamicRouter:
def __init__(self):
self.handlers = self._load_handlers()
def route(self, intent: str, context: RoutingContext) -> Handler:
# VIP 고객은 전담 핸들러로 라우팅
if context.user_tier == "vip":
return self.handlers.get(f"{intent}_vip", self.handlers[intent])
# 야간(22시~8시)에는 자동 응답 핸들러 사용
hour = context.timestamp.hour
if hour >= 22 or hour < 8:
return self.handlers.get(f"{intent}_auto", self.handlers[intent])
# 시스템 부하가 높으면 간소화된 핸들러 사용
if context.system_load > 0.8:
return self.handlers.get(f"{intent}_lite", self.handlers[intent])
return self.handlers[intent]
김개발 씨의 챗봇이 어느 정도 완성되었습니다. 의도 분류도 잘 되고, 각 핸들러도 제 역할을 했습니다.
하지만 운영팀에서 새로운 요구사항이 들어왔습니다. "VIP 고객은 더 친절하고 상세한 응답을 받아야 해요.
그리고 새벽 시간에는 상담원이 없으니까 자동 응답으로 처리해 주세요." 김개발 씨는 고민에 빠졌습니다. 지금 구조에서는 의도만으로 핸들러를 선택하는데, 여기에 추가 조건을 넣으려면 코드가 복잡해질 것 같았습니다.
박시니어 씨가 조언했습니다. "라우팅 컨텍스트를 도입해 봐.
의도뿐만 아니라 상황 정보도 함께 전달하는 거야." RoutingContext는 라우팅 결정에 필요한 모든 상황 정보를 담는 객체입니다. 사용자 정보, 요청 시간, 시스템 상태 등을 하나로 묶어서 전달합니다.
이 접근법의 장점은 명확합니다. 라우팅 로직이 한 곳에 집중됩니다.
새로운 조건이 추가되어도 DynamicRouter 클래스만 수정하면 됩니다. 핸들러는 자신이 어떤 조건으로 선택되었는지 알 필요가 없습니다.
위의 코드에서 라우팅 우선순위를 살펴보겠습니다. 첫 번째로 VIP 여부를 확인합니다.
VIP 고객이라면 전용 핸들러가 있는지 찾아보고, 있으면 그것을 사용합니다. 없으면 일반 핸들러를 사용합니다.
get 메서드의 두 번째 인자가 기본값 역할을 하는 것입니다. 두 번째로 시간대를 확인합니다.
야간에는 자동 응답 핸들러를 우선적으로 사용합니다. 이렇게 하면 상담원 없이도 기본적인 응대가 가능합니다.
세 번째로 시스템 부하를 확인합니다. 트래픽이 몰려서 서버가 힘들 때는 간소화된 핸들러를 사용합니다.
응답 품질은 조금 떨어지지만 시스템 안정성을 확보할 수 있습니다. 실무에서 이런 동적 라우팅은 매우 흔합니다.
이커머스 플랫폼에서는 블랙프라이데이 같은 대규모 세일 기간에 시스템 부하가 급증합니다. 이때 모든 요청을 똑같이 처리하면 서버가 다운될 수 있습니다.
동적 라우팅으로 일부 요청을 간소화된 처리로 우회시키면 전체 서비스의 안정성을 유지할 수 있습니다. 주의할 점도 있습니다.
라우팅 조건이 너무 복잡해지면 어떤 핸들러가 선택될지 예측하기 어려워집니다. 디버깅도 힘들어집니다.
조건은 가능하면 단순하게 유지하고, 라우팅 결정 로그를 남겨두는 것이 좋습니다. 김개발 씨가 말했습니다.
"그러니까 내비게이션이 실시간 교통 정보를 반영해서 경로를 추천하는 것처럼, 우리 시스템도 상황에 맞게 유연하게 처리하는 거군요!" 동적 라우팅은 복잡한 비즈니스 요구사항을 우아하게 처리할 수 있는 강력한 도구입니다.
실전 팁
💡 - 라우팅 결정 로그를 남겨 추후 분석과 디버깅에 활용하세요
- 조건의 우선순위를 명확히 정의하고 문서화하세요
- 핸들러 이름 규칙을 일관되게 유지하면 관리가 편해집니다
4. Fallback 처리
어느 날 김개발 씨의 챗봇에 예상치 못한 입력이 들어왔습니다. "날씨 어때요?" 환불, 배송, 상품 문의 중 어디에도 해당하지 않는 질문이었습니다.
시스템은 에러를 뿜었고, 사용자는 아무 응답도 받지 못했습니다. "이런 경우는 어떻게 처리해야 하죠?"
Fallback 처리는 시스템이 적절한 핸들러를 찾지 못했을 때 실행되는 대안적인 처리 로직입니다. 마치 모든 전화를 받는 교환원이나, "그 외 문의는 0번을 눌러주세요"와 같은 역할입니다.
예외 상황에서도 사용자에게 적절한 응답을 제공하여 서비스 품질을 유지합니다.
다음 코드를 살펴봅시다.
class FallbackHandler:
def __init__(self, llm):
self.llm = llm
self.escalation_threshold = 3
def process(self, user_input: str, context: dict) -> Response:
# 같은 사용자가 반복해서 Fallback에 빠지면 상담원 연결
if context.get("fallback_count", 0) >= self.escalation_threshold:
return self._escalate_to_human(user_input, context)
# 일반적인 Fallback 응답
response = self.llm.invoke(f"""
사용자의 질문에 친절하게 답하되, 우리 서비스 범위를 안내해주세요.
질문: {user_input}
서비스 범위: 환불, 배송, 상품 문의
범위 외 질문이면 정중히 안내하세요.""")
return Response(
message=response.content,
handler_used="fallback",
confidence=0.5
)
def _escalate_to_human(self, user_input: str, context: dict) -> Response:
return Response(message="상담원에게 연결해 드리겠습니다.", handler_used="escalation")
김개발 씨는 당황했습니다. 분명히 모든 케이스를 처리했다고 생각했는데, "날씨 어때요?"라는 질문 하나에 시스템이 먹통이 되었습니다.
박시니어 씨가 말했습니다. "사용자는 예측할 수 없어.
아무리 완벽하게 설계해도 예상 밖의 입력은 반드시 들어와. 그래서 Fallback이 필요한 거야." Fallback은 한마디로 "안전망"입니다.
고속도로 옆에 있는 비상 차선을 생각해 보세요. 평소에는 쓸 일이 없지만, 문제가 생겼을 때 차가 멈출 수 있는 공간이 있어야 대형 사고를 막을 수 있습니다.
좋은 Fallback 처리는 단순히 "죄송합니다, 이해하지 못했습니다"로 끝나지 않습니다. 위의 코드에서 중요한 점을 살펴보겠습니다.
첫째, 반복 카운트 추적입니다. 사용자가 계속해서 Fallback에 빠진다면, 그건 시스템의 한계입니다.
이때는 사람이 개입해야 합니다. escalation_threshold를 3으로 설정해서, 세 번 연속 Fallback이면 상담원에게 연결합니다.
둘째, 서비스 범위 안내입니다. "날씨 어때요?"라는 질문에 단순히 "모르겠어요"라고 하면 사용자는 답답합니다.
대신 "저는 환불, 배송, 상품 문의를 도와드리고 있어요. 날씨 정보는 기상청 앱을 확인해 주세요"라고 하면 훨씬 친절합니다.
셋째, 응답 메타데이터입니다. handler_used와 confidence 필드를 통해 이 응답이 Fallback에서 나왔다는 것을 기록합니다.
나중에 로그를 분석해서 어떤 질문들이 Fallback으로 빠지는지 파악할 수 있습니다. 실무에서 Fallback 데이터는 금광입니다.
Fallback으로 빠진 질문들을 모아보면 사용자들이 실제로 원하는 것이 무엇인지 알 수 있습니다. "날씨 어때요?"가 자주 들어온다면, 날씨 API를 연동하는 것을 고려해 볼 수 있습니다.
"매장 위치 알려줘요"가 많다면, 매장 안내 핸들러를 추가하면 됩니다. 주의할 점도 있습니다.
Fallback 응답이 너무 자주 나오면 사용자 경험이 나빠집니다. 전체 요청 중 Fallback 비율을 모니터링하세요.
10%가 넘어가면 의도 분류나 핸들러 커버리지에 문제가 있는 것입니다. 김개발 씨가 고개를 끄덕였습니다.
"그러니까 Fallback은 단순한 에러 처리가 아니라, 시스템을 개선할 수 있는 인사이트를 주는 거군요!" Fallback은 실패가 아닙니다. 더 나은 시스템을 만들기 위한 피드백입니다.
실전 팁
💡 - Fallback 비율을 지속적으로 모니터링하고 10% 이하로 유지하세요
- Fallback으로 들어온 질문을 분석해 새로운 핸들러 추가 여부를 결정하세요
- 반복적인 Fallback은 반드시 사람에게 에스컬레이션하세요
5. 라우팅 룰 관리
시스템이 잘 동작하자 김개발 씨에게 새로운 요구사항이 쏟아졌습니다. "반품 핸들러도 추가해 주세요", "VIP 조건을 월 구매액 100만원 이상으로 바꿔주세요", "이벤트 기간에는 프로모션 안내를 먼저 해주세요".
매번 코드를 수정하고 배포하는 것이 번거로워졌습니다.
라우팅 룰 관리는 라우팅 규칙을 코드에서 분리하여 설정 파일이나 데이터베이스로 관리하는 방식입니다. 마치 교통 신호 시스템의 신호 주기를 현장에서 조정할 수 있는 것처럼, 개발자가 아닌 운영자도 규칙을 변경할 수 있게 됩니다.
배포 없이 실시간으로 라우팅 동작을 바꿀 수 있어 운영 효율성이 크게 향상됩니다.
다음 코드를 살펴봅시다.
import yaml
from datetime import datetime
# routing_rules.yaml 파일에서 규칙을 로드합니다
class RuleBasedRouter:
def __init__(self, rules_path: str = "routing_rules.yaml"):
self.rules = self._load_rules(rules_path)
def _load_rules(self, path: str) -> dict:
with open(path, "r", encoding="utf-8") as f:
return yaml.safe_load(f)
def route(self, intent: str, context: dict) -> str:
# 규칙을 우선순위 순으로 평가합니다
for rule in self.rules.get("rules", []):
if self._evaluate_rule(rule, intent, context):
return rule["handler"]
# 기본 핸들러 반환
return self.rules.get("default_handlers", {}).get(intent, "fallback")
def _evaluate_rule(self, rule: dict, intent: str, context: dict) -> bool:
conditions = rule.get("conditions", {})
if "intent" in conditions and conditions["intent"] != intent:
return False
if "user_tier" in conditions and context.get("user_tier") not in conditions["user_tier"]:
return False
if "time_range" in conditions and not self._check_time_range(conditions["time_range"]):
return False
return True
김개발 씨의 고민이 깊어졌습니다. 요구사항이 들어올 때마다 코드를 수정하고, 테스트하고, 배포해야 했습니다.
간단한 조건 하나 바꾸는 데도 반나절이 걸렸습니다. 박시니어 씨가 물었습니다.
"김 개발자, 운영팀에서 직접 규칙을 바꿀 수 있으면 어떨 것 같아?" 김개발 씨는 눈이 번쩍 뜨였습니다. "그게 가능한가요?" 물론 가능합니다.
핵심은 규칙과 로직의 분리입니다. 라우팅 규칙을 코드에 하드코딩하지 않고, 별도의 설정 파일로 빼는 것입니다.
YAML 파일이 대표적인 선택입니다. 사람이 읽기 쉽고, 수정하기도 편합니다.
아래와 같은 형태로 규칙을 정의할 수 있습니다. yaml rules: - name: vip_refund_priority conditions: intent: refund user_tier: [vip, premium] handler: refund_vip priority: 1 - name: night_auto_response conditions: time_range: "22:00-08:00" handler: auto_response priority: 2 default_handlers: refund: refund_standard shipping: shipping_standard 위의 코드에서 RuleBasedRouter는 이 YAML 파일을 읽어서 라우팅 결정을 내립니다.
route 메서드는 규칙들을 순서대로 평가하며, 조건에 맞는 첫 번째 규칙의 핸들러를 반환합니다. 이 방식의 장점은 명확합니다.
첫째, 배포 없는 변경입니다. YAML 파일만 수정하면 됩니다.
서버를 재시작하거나 코드를 배포할 필요가 없습니다. 운영 중에도 규칙을 바꿀 수 있습니다.
둘째, 비개발자도 수정 가능합니다. YAML은 직관적입니다.
운영팀이나 기획팀에서 직접 규칙을 추가하거나 수정할 수 있습니다. 셋째, 버전 관리가 쉽습니다.
설정 파일을 Git으로 관리하면 누가 언제 무엇을 바꿨는지 추적할 수 있습니다. 문제가 생기면 이전 버전으로 롤백하면 됩니다.
실무에서 한 단계 더 나아가면 관리 UI를 만들 수 있습니다. 웹 대시보드에서 규칙을 추가, 수정, 삭제할 수 있게 하면 더 편리합니다.
변경 이력도 보여주고, A/B 테스트도 가능하게 만들 수 있습니다. 주의할 점도 있습니다.
규칙이 많아지면 충돌이 생길 수 있습니다. 같은 조건에 여러 규칙이 매칭되면 어떤 것이 적용될까요?
우선순위를 명확히 정의하고, 규칙 검증 로직을 추가하는 것이 좋습니다. 김개발 씨가 말했습니다.
"이제 운영팀에서 새 규칙 추가해달라고 해도 걱정 없겠네요. 그분들이 직접 하시면 되니까요!" 라우팅 룰 관리는 시스템의 유연성을 크게 높여줍니다.
처음부터 이 구조를 고려하면 나중에 훨씬 편해집니다.
실전 팁
💡 - 규칙 우선순위를 명확히 정의하고 충돌 방지 로직을 추가하세요
- 규칙 변경 시 검증 단계를 거치도록 설계하세요
- 변경 이력을 반드시 기록하고, 롤백 기능을 준비해 두세요
6. 실전 고객 문의 자동 분류
이제 모든 개념을 배운 김개발 씨가 실전 프로젝트를 시작합니다. 쇼핑몰 고객센터에 들어오는 다양한 문의를 자동으로 분류하고, 적절한 처리기로 연결하는 시스템을 구축해 보겠습니다.
지금까지 배운 모든 내용을 하나로 통합하는 시간입니다.
실전 고객 문의 자동 분류 시스템은 의도 분류, 동적 라우팅, Fallback 처리, 룰 관리를 모두 통합한 완성형 Routing Workflow입니다. 실제 운영 환경에서 사용할 수 있는 수준의 시스템을 단계별로 구축하며, 앞서 배운 개념들이 어떻게 조화를 이루는지 확인합니다.
다음 코드를 살펴봅시다.
class CustomerInquiryRouter:
def __init__(self):
self.classifier = IntentClassifier()
self.rule_router = RuleBasedRouter("customer_rules.yaml")
self.handlers = self._init_handlers()
self.fallback = FallbackHandler(llm=ChatOpenAI())
def process_inquiry(self, message: str, user_info: dict) -> Response:
# 1단계: 의도 분류
intent = self.classifier.classify(message)
# 2단계: 라우팅 컨텍스트 구성
context = RoutingContext(
user_id=user_info["id"],
user_tier=user_info.get("tier", "standard"),
timestamp=datetime.now(),
system_load=self._get_system_load()
)
# 3단계: 규칙 기반 핸들러 선택
handler_name = self.rule_router.route(intent, context.__dict__)
handler = self.handlers.get(handler_name)
# 4단계: 핸들러가 없으면 Fallback
if not handler:
return self.fallback.process(message, context.__dict__)
# 5단계: 핸들러 실행 및 결과 반환
return handler.process(message, context)
드디어 실전입니다. 김개발 씨는 지금까지 배운 모든 것을 하나로 엮어야 했습니다.
머릿속에서 각 조각들이 어떻게 맞춰지는지 그려보기 시작했습니다. 박시니어 씨가 조언했습니다.
"전체 흐름을 먼저 그려봐. 사용자 메시지가 들어왔을 때 어떤 순서로 처리되는지 말이야." 전체 흐름은 다섯 단계로 이루어집니다.
1단계: 의도 분류. 사용자가 "환불 받고 싶어요"라고 입력하면, IntentClassifier가 이를 분석해서 "refund"라는 의도로 분류합니다. 2단계: 컨텍스트 구성. 단순히 의도만으로는 부족합니다.
이 사용자가 VIP인지, 지금이 몇 시인지, 서버 부하는 어떤지 등의 정보를 모아 RoutingContext를 만듭니다. 3단계: 규칙 기반 라우팅. RuleBasedRouter가 YAML 파일의 규칙들을 확인합니다.
VIP 사용자의 환불 요청이니 "refund_vip" 핸들러를 선택합니다. 4단계: Fallback 확인. 혹시 해당 핸들러가 없다면 Fallback으로 빠집니다.
시스템이 멈추지 않고 최소한의 응답이라도 할 수 있도록 합니다. 5단계: 처리 및 응답. 선택된 핸들러가 실제 업무를 처리합니다.
환불 정책을 안내하거나, 환불 신청 양식을 보여주거나, 상담원에게 연결하는 등의 작업을 수행합니다. 코드를 자세히 살펴보면 각 컴포넌트가 독립적으로 동작한다는 것을 알 수 있습니다.
IntentClassifier는 분류만 합니다. 어떤 핸들러가 있는지, 어떤 규칙이 있는지 모릅니다.
RuleBasedRouter는 규칙 평가만 합니다. 의도 분류가 어떻게 되었는지, 핸들러가 무엇을 하는지 모릅니다.
이런 관심사의 분리가 유지보수를 쉽게 만듭니다. 의도 분류 로직을 개선하고 싶다면 IntentClassifier만 수정하면 됩니다.
새 규칙을 추가하고 싶다면 YAML 파일만 수정하면 됩니다. 실제 운영에서 추가로 고려해야 할 것들이 있습니다.
로깅. 모든 단계에서 로그를 남겨야 합니다. 어떤 의도로 분류되었고, 어떤 규칙이 적용되었고, 어떤 핸들러가 선택되었는지 기록해야 나중에 문제를 분석할 수 있습니다.
모니터링. 의도별 분포, Fallback 비율, 핸들러별 처리 시간 등을 대시보드로 만들어 실시간으로 확인할 수 있어야 합니다. A/B 테스트. 새로운 핸들러나 규칙을 적용할 때 일부 트래픽에만 먼저 적용해서 효과를 검증할 수 있으면 좋습니다.
김개발 씨가 완성된 시스템을 바라보며 뿌듯해했습니다. "처음에는 막막했는데, 하나씩 배우다 보니 전체 그림이 보이네요." 박시니어 씨가 웃으며 말했습니다.
"이게 시작이야. 실제 운영하면서 더 많이 배우게 될 거야.
사용자는 늘 예상을 벗어나니까." Routing Workflow는 AI 에이전트 시스템의 핵심입니다. 이 패턴을 잘 익혀두면 어떤 복잡한 요구사항도 체계적으로 해결할 수 있습니다.
실전 팁
💡 - 각 컴포넌트를 독립적으로 테스트할 수 있도록 설계하세요
- 프로덕션 배포 전 충분한 통합 테스트를 수행하세요
- 처음부터 로깅과 모니터링을 고려하면 나중에 고생이 줄어듭니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
vLLM 통합 완벽 가이드
대규모 언어 모델 추론을 획기적으로 가속화하는 vLLM의 설치부터 실전 서비스 구축까지 다룹니다. PagedAttention과 연속 배칭 기술로 GPU 메모리를 효율적으로 활용하는 방법을 배웁니다.
Web UI Demo 구축 완벽 가이드
Gradio를 활용하여 머신러닝 모델과 AI 서비스를 위한 웹 인터페이스를 구축하는 방법을 다룹니다. 코드 몇 줄만으로 전문적인 데모 페이지를 만들고 배포하는 과정을 초급자도 쉽게 따라할 수 있도록 설명합니다.
Sandboxing & Execution Control 완벽 가이드
AI 에이전트가 코드를 실행할 때 반드시 필요한 보안 기술인 샌드박싱과 실행 제어에 대해 알아봅니다. 격리된 환경에서 안전하게 코드를 실행하고, 악성 동작을 탐지하는 방법을 단계별로 설명합니다.
Voice Design then Clone 워크플로우 완벽 가이드
AI 음성 합성에서 일관된 캐릭터 음성을 만드는 Voice Design then Clone 워크플로우를 설명합니다. 참조 음성 생성부터 재사용 가능한 캐릭터 구축까지 실무 활용법을 다룹니다.
Tool Use 완벽 가이드 - Shell, Browser, DB 실전 활용
AI 에이전트가 외부 도구를 활용하여 셸 명령어 실행, 브라우저 자동화, 데이터베이스 접근 등을 수행하는 방법을 배웁니다. 실무에서 바로 적용할 수 있는 패턴과 베스트 프랙티스를 담았습니다.