WhatsApp 에이전트가 제멋대로 행동하게 두는 것을 멈췄더니 모든 것이 좋아졌다
요약
WhatsApp 챗봇 구축 시 모든 메시지를 LLM에 직접 연결하는 대신, 먼저 이벤트를 분류하는 디스패처(Dispatcher) 레이어를 도입해야 합니다. 이를 통해 토큰 비용을 절감하고, 응답의 정확도를 높이며, 효율적인 상담원 전환이 가능해집니다.
핵심 포인트
- 메시지를 대화가 아닌 이벤트로 먼저 취급하여 분류(Triage)할 것
- 단일 모놀리식 에이전트 구조는 토큰 낭비와 지연 시간을 유발함
- 웹훅 데이터를 활용해 LLM 호출 전 사전 결정 수행
- 비용 효율적인 운영을 위해 디스패처 레이어 구축 필수
저는 똑같은 WhatsApp 봇 실수를 계속 목격하고 있습니다:
누군가가 Twilio나 Meta 웹훅 (webhook)을 GPT-5, Claude, 또는 OpenClaw 루프에 직접 연결하고, 쾌활한 시스템 프롬프트 (system prompt)를 작성한 뒤, 그것을 아키텍처 (architecture)라고 부릅니다.
데모 (demo)용으로는 작동합니다.
하지만 프로덕션 (production) 환경에서는 이상해집니다.
실제로 버텨내는 패턴은 훨씬 덜 흥미롭습니다:
먼저 분류(triage)하고, 그다음 채팅(chat)하라
이는 들어오는 WhatsApp 메시지를 대화로 취급하기 전에 하나의 이벤트 (event)로 취급해야 함을 의미합니다.
제가 WhatsApp 봇을 그런 방식으로 생각하기 시작하자, 여러 문제들이 동시에 쉬워졌습니다:
- LLM 사용량 감소
- 프롬프트 확산 (prompt sprawl) 감소
- 명백한 사례에 대한 멍청한 답변 감소
- 더 나은 상담원 전환 (human handoff)
- WhatsApp의 2025년 템플릿 가격 정책에 따른 더 예측 가능한 동작
저는 Make에서의 WhatsApp 챗봇 워크플로 (workflow)에 관한 r/openclaw의 작은 스레드를 통해 이 점을 다시 상기하게 되었습니다. 워크플로 자체는 괜찮았습니다. 더 큰 교훈은 대부분의 사람들이 잘못된 레이어 (layer)에서 시작한다는 것이었습니다.
그들은 어시스턴트 (assistant)부터 시작합니다.
그들은 디스패처 (dispatcher)부터 시작해야 합니다.
웹훅은 이미 라우팅 신호를 제공합니다
Meta WhatsApp Cloud API를 사용한다면, 인바운드 웹훅 (inbound webhook)은 어떤 LLM 호출이 일어나기도 전에 이미 많은 것을 알려줍니다:
- 발신자 (sender)
- 메시지 유형 (message type)
- 타임스탬프 (timestamp)
- 텍스트 본문 (text body)
- 미디어 페이로드 (media payload)
- 이벤트가 메시지인지 상태 업데이트 (status update)인지 여부
최소한의 예시:
{
"object": "whatsapp_business_account",
"entry": [
...
Twilio WhatsApp을 사용한다면 다음과 같은 폼 필드 (form fields)를 받게 됩니다:
MessageSidFromToBodyNumMedia
이것만으로도 모델 호출 비용을 태우지 않고도 몇 가지 결정을 내리기에 충분합니다.
예를 들어:
- 캡션이 없는 사진
- 배송/상태 이벤트 (delivery/status event)
- 알려진 VIP 발신자
- 명백한 결제 요청
- 스팸 (spam)
- 영업 시간 외
- 주문 상태 조회
이 중 어느 것도 거대하고 개방적인 프롬프트 (open-ended prompt)로 바로 들어가서는 안 됩니다.
안티 패턴 (anti-pattern): 하나의 에이전트가 모든 것을 수행하는 것
이 부분이 토큰 낭비, 지연 시간 (latency), 그리고 취약한 동작을 유발하는 원인입니다.
단일 모놀리식 에이전트 (monolithic agent)는 결국 보지 말았어야 할 작업까지 떠맡게 됩니다:
- 읽음 확인 (reading receipts)
- 상태 노이즈 해석 (interpreting status noise)
- 결제 흐름에서의 즉흥적 대응 (improvising on billing flows)
- 비용이 많이 드는 추론 (expensive reasoning)을 사용하여 반복적이고 위험도가 낮은 질문에 답변하기
- 컨텍스트를 소모하며 인간에게 넘기지 않으려 애쓴 후에야 인간에게 넘길지 말지 결정하기
이것은 지능이 아닙니다.
그것은 잘못된 라우팅 (bad routing)입니다.
사용량 분석 (usage analytics)을 보면서 "왜 이 녀석이 쓰레기 같은 일에 토큰을 쓰고 있지?"라고 생각한 적이 있다면, 그 답은 대개 상류 (upstream)에 있습니다.
모니터링이 도움이 되는 것은 맞습니다.
openclaw status --usage
하지만 사용량 대시보드는 사후 부검 (autopsy)일 뿐입니다.
라우팅 (Routing)이 치료제입니다.
지루한 아키텍처가 승리한다
대부분의 WhatsApp 고객 지원 및 리드 수집 (lead-intake) 워크플로우의 경우, 저는 매번 순수한 채팅 루프 (chat loop) 대신 n8n을 선택할 것입니다.
n8n이 마법 같아서가 아닙니다.
지루하지만 올바른 결정을 내리기 쉽게 만들어주기 때문입니다.
여러분은 다음과 같은 작업을 할 수 있습니다:
- 인바운드 페이로드 (inbound payloads) 정규화
- 결정론적 신호 (deterministic signals)에 따른 분기 처리
- 실제로 해석이 필요한 메시지만 분류
- 불분명한 케이스를 명시적인 폴백 (fallback)으로 전송
- 첫 접점의 혼란 대신, 범위가 정해진 작업에만 GPT-5나 Claude를 예약
이것이 훨씬 더 건강한 설계입니다.
n8n에서의 실용적인 우선 분류 (triage-first) 흐름
제가 배포할 패턴은 다음과 같습니다.
- Webhook이 Meta 또는 Twilio 이벤트를 수신합니다.
- Code node가 페이로드를 하나의 형태로 정규화합니다.
- Switch node가 명백한 비-LLM 케이스를 라우팅합니다.
- Text Classifier가 실제 텍스트 메시지만 라벨링합니다.
- CRM / 큐 (queue) / 결정론적 답변이 알려진 카테고리를 처리합니다.
- LLM 단계는 추론이나 초안 작성이 필요한 분기에 대해서만 실행됩니다.
정규화된 객체 예시:
{
channel: "whatsapp",
from: "16505551234",
...
n8n을 위한 정규화 코드 예시:
const body = $json.body || $json;
const isTwilio = !!body.MessageSid;
...
그러면 Switch 로직을 통해 쉬운 케이스들을 즉시 떼어낼 수 있습니다:
eventType === "status"messageType !== "text"- 빈 텍스트
- 알려진 VIP 발신자
- 영업시간 외
오직 그 후에만 분류 (classification)가 일어나야 합니다.
철학적으로가 아니라, 좁게 분류하라
이 지점에서 사람들이 과하게 구축하곤 합니다.
여기서는 뛰어난 에이전트 (agent)가 필요하지 않습니다.
신뢰할 수 있는 라우터 (router)가 필요할 뿐입니다.
좋은 카테고리 (category)란 지루한 것입니다:
sales(영업)support(지원)billing(결제)spam(스팸)human_handoff(상담원 연결)other(기타)
이것만으로도 불필요한 모델 (model) 작업을 대폭 줄일 수 있습니다.
리드 수집 (lead intake)을 위해 n8n Text Classifier를 사용하고 있다면, 저는 보통 하나의 주요 클래스 (class)만 유지할 것입니다.
많은 워크플로 (workflow)에서, 중첩되는 의도 (intent)를 파악하는 것보다 결정적인 다음 단계 (next step)를 정하는 것이 더 유용합니다.
예시:
pricing_request(가격 문의) → 가격 정보 전송 또는 CRM 태스크 생성urgent_customer_issue(긴급 고객 문제) → 상담원 대기열billing_problem(결제 문제) → 결제 워크플로other(기타) → 폴백 (fallback) 검토
이는 기본적으로 이메일 분류 자동화와 동일한 교훈을 줍니다:
비싼 개방형 추론 (open-ended reasoning)을 피하기 위해 가벼운 분류 (categorization)를 사용하십시오.
이것이 지금 더 중요한 이유: WhatsApp 가격 정책 변화가 문제의 형태를 바꾸었습니다
이것은 이제 단순한 엔지니어링 선호도의 문제가 아닙니다.
2025년 WhatsApp Business Platform의 가격 정책 변화로 인해, 템플릿 메시지 (template messages)는 전송된 메시지당 비용이 부과되는 반면, 비템플릿 메시지 (non-template messages)는 고객 서비스 창 (customer service window) 내에서는 무료입니다. 일부 경우에는 무료 진입점 창 (entry point window)도 존재합니다.
이는 제가 봇 (bot) 설계를 생각하는 방식을 바꿉니다.
채팅 우선 (chat-first) 봇은 언어를 생성하는 것부터 시작합니다.
트리아지 우선 (triage-first) 워크플로는 무엇이 일어났는지 결정하는 것부터 시작합니다.
이 차이가 중요한 이유는 이제 워크플로가 다음과 같은 질문을 던질 수 있기 때문입니다:
- 이것이 서비스 창 (service window) 안에 있는가?
- 템플릿이 아예 필요한가?
- 이 메시지가 유료로 전환될 경우 보낼 가치가 있는가?
- 아무것도 하지 않고 상담원을 기다려야 하는가?
이것은 아키텍처 (architecture)가 비용 동작에 직접적으로 영향을 미치는 사례입니다.
Meta vs Twilio vs n8n: 실제로 무엇이 바뀌는가?
| 옵션 | 트리아지 우선 (triage-first) 설계에서 중요한 점 |
|---|---|
| Meta WhatsApp Cloud API | 네이티브 웹훅 (webhook) 페이로드 (payload)가 인바운드 messages와 아웃바운드 statuses를 명확하게 구분합니다. 직접적인 제어와 서비스 창을 인지하는 로직 (service-window-aware logic)을 원한다면 좋습니다. |
| ... |
개발자들이 보통 신경 쓰는 몇 가지 실무적인 참고 사항입니다:
개발자들이 보통 신경 쓰는 몇 가지 실무적인 참고 사항입니다:
- n8n의 Webhook 노드 기본 최대 페이로드 크기는
N8N_PAYLOAD_SIZE_MAX를 변경하지 않으면 16MB입니다. - Twilio는 발신자당 처리량 제한(per-sender throughput limits) 문서를 제공하며, 이는 향후 아웃바운드 메시징을 확장할 때 중요합니다.
- 이 두 가지 모두 첫 단계가 “모든 것을 모델로 보내기”라면 아무 도움이 되지 않습니다.
제가 가장 먼저 구축하고 싶은 것들
단 하나의 ‘친근한 어시스턴트’ 프롬프트를 작성하기 전에, 저는 이 세 가지 플로우를 구축할 것입니다.
1) 지원 티어 분류(Support triage)
if status event -> LLM 무시
if media and no caption -> 미디어 검토 대기열
if known customer -> CRM 컨텍스트 가져오기
...
2) 리드 접수(Lead intake)
if pricing request -> 결정론적 응답 또는 CRM 업데이트
if urgent issue -> 인간 담당자 대기열
if obvious spam -> 삭제
...
3) 서비스 기간 인지형 답변(Service-window-aware replies)
if inside service window -> 템플릿이 아닌 응답 선호
if outside service window -> 템플릿 사용의 정당성 결정
if low-value follow-up -> 청구되지 않는 아무 행동도 하지 않기
Node에서 최소 라우터 예시
만약 n8n을 사용하지 않고 코드로 핵심 로직만 원한다면, 기본적인 아이디어는 다음과 같습니다:
function routeMessage(msg) {
if (msg.eventType === 'status') {
return { action: 'ignore_status' };
...
이 하나의 함수가 대부분의 실제 시스템에서 더 화려한 프롬프트보다 돈을 많이 절약해 줄 것입니다.
Standard Compute가 적합한 곳
모든 WhatsApp 이벤트를 거대한 에이전트에게 보내는 것을 멈추면, LLM 호출이 훨씬 깔끔해집니다.
바로 이때 OpenAI와 호환되는 엔드포인트가 실제로 유용합니다:
- 분류(classification) 호출
- 범위 지정 초안 작성(scoped drafting)
- 적절한 분기에서의 지원 답변
- 결정론적 라우팅이 소진되었을 때의 폴백 추론(fallback reasoning)
n8n, Make, Zapier, OpenClaw, 또는 사용자 지정 Node/Python 워크플로우를 구축하는 경우, Standard Compute는 OpenAI API의 드롭인 대체재입니다.
동일한 SDK 형태. 평평한 월별 가격. 토큰당 과금 없음.
이것은 에이전트 워크플로우에 매우 중요합니다. 왜냐하면 잘 라우팅된 시스템조차도 시간이 지남에 따라 많은 모델 트래픽을 생성하기 때문입니다.
차이점은 이제 모든 웹훅 (webhook)에 대해 봇이 자유롭게 행동하게 두는 대신, 모델 호출 (model calls)을 유용한 작업에 사용한다는 것입니다.
더 중요한 것은, 하루 종일 토큰 사용량 (token usage)을 감시하지 않고도 자동화 (automations)를 구축할 수 있다는 점입니다.
나의 경험칙 (My rule of thumb)
만약 당신의 봇이 하루에 메시지를 5개 정도 받고, 가끔 횡설수설하더라도 아무도 신경 쓰지 않는다면, 채팅 우선 (chat-first) 방식도 괜찮습니다.
하지만 다음과 같은 영역을 다룬다면:
- 고객 지원 (support)
- 리드 자격 검증 (lead qualification)
- 결제 (billing)
- 상담원 연결 (human handoff)
- 사람들이 신뢰해야 하는 모든 워크플로우 (workflow)
그렇다면 채팅 우선 방식은 함정입니다.
내가 본 최고의 WhatsApp 에이전트들은 챗봇 (chatbots)이라기보다 배차원 (dispatchers)에 더 가깝게 느껴집니다.
그들은 다음과 같은 일을 수행합니다:
- 메시지 유형 감지 (detect message type)
- 발신자 확인 (check who sent it)
- 서비스 운영 시간 확인 (know whether the service window is open)
- 미디어와 텍스트를 다르게 라우팅 (route media differently from text)
- 결제와 영업 분리 (separate billing from sales)
- VIP 에스컬레이션 (escalate VIPs)
- 상태 노이즈 무시 (ignore status noise)
그 후, GPT-5, Claude, Llama 또는 Qwen이 마침내 호출될 때, 모델은 깔끔하게 정리된 작업을 받게 됩니다.
이것이 바로 시스템이 더 똑똑하게 느껴지는 이유입니다.
프롬프트 (prompt)가 더 좋아졌기 때문이 아닙니다.
워크플로우 (workflow)가 단 하나의 에이전트에게 회사 전체의 역할을 요구하는 것을 멈췄기 때문입니다.
만약 당신이 지금 WhatsApp 봇을 구축하고 있다면, 나의 조언은 간단합니다:
철학자를 고용하기 전에 접수원 (receptionist)부터 만드세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기