DigiKedai 텔레그램 AI 봇 구축기
요약
본 글은 디지털 제품 판매를 하는 1인 기업을 위해, 고객의 반복적인 질문에 자동으로 응답하고 심지어 무료 체험 계정까지 제공하는 AI 고객 지원 봇 구축 과정을 다룹니다. 이 봇은 인건비 절감과 24/7 즉각적인 대응이 가능하여 소규모 비즈니스의 확장성을 혁신적으로 개선합니다.
핵심 포인트
- AI 챗봇은 단순한 답변을 넘어 업셀링까지 수행할 수 있습니다.
- 인간 개입 없이 연중무휴로 고객 지원 및 판매가 가능해집니다.
- TypeScript, grammY, Hono 등을 활용하여 안정적인 아키텍처를 구축했습니다.
- LiteLLM 같은 게이트웨이를 사용해 모델 공급자를 유연하게 관리합니다.

원래 제 블로그에 게시된 글입니다 — 전체 맥락, 코드 및 업데이트와 함께 여기서 읽기.
Digi Kedai는 온라인 강좌, 전자책, 템플릿 등 디지털 제품을 판매합니다. 그리고 모든 판매 과정에는 동일한 고객 질문의 흐름이 발생합니다: "이 강좌에 무료 미리보기가 있나요?", "계정은 어떻게 얻나요?", "어떤 패키지를 사야 하나요?". 이러한 질문들에 수동으로 답변하는 것은 1인 기업에게는 확장성이 없습니다. 그래서 저는 인간 개입 없이도 연중무휴로 해당 질문에 답하고 심지어 무료 체험 계정까지 제공할 수 있는 AI 고객 지원 봇을 구축했습니다.
이것은 그 과정의 이야기입니다 — 아키텍처, LLM(대규모 언어 모델) 연결 구조, 그리고 오후 시간을 잡아먹었던 버그들에 대한 이야기입니다.
왜 비즈니스가 이런 봇을 필요로 하는가
아키텍처를 논하기 전에, '왜'라는 질문부터 던져야 합니다. 지원 봇은 단순한 장난감이 아닙니다 — 이는 소규모 비즈니스의 경제 구조 자체를 변화시킵니다:
- 인건비 절감. 봇이 답변하는 모든 일상적인 질문 하나는 팀이 처리할 필요가 없는 업무입니다. Digi Kedai는 직원 한 명을 고용하는 비용의 일부만으로 연중무휴 지원 담당자와 동등한 효과를 얻습니다.
- 24/7 즉각적인 응답. 고객들은 자정이나 주말에 질문합니다. 봇은 지연 없이, 그들의 언어로 몇 초 만에 답합니다 — 대기열도 없고, "나중에 연락드리겠습니다"라는 말도 없습니다.
- 브라우저를 구매자로 전환. 이 봇은 단순히 답변만 하는 것이 아니라, '업셀링(upsell)'까지 합니다. "무료 체험이 있나요?"라는 질문은 인간의 개입 없이 몇 번의 터치만으로 실제로 사용 가능한 체험 계정으로 이어집니다.
- 고객을 절대 잊지 않음. 장기 기억 기능 덕분에 반복 구매 고객들은 낯선 사람이 아닌 단골손님처럼 환영받습니다.
- 카탈로그 규모에 따라 확장 가능. 제품을 추가하면 봇은 이미 그 제품을 알고 있습니다 — 재교육이나 새로운 FAQ 페이지가 필요 없습니다.
Digi Kedai 같은 1인 사업자에게는, 새벽 2시에 판매를 놓치느냐 아니면 성사시키느냐의 차이입니다.
스택 (The stack)
- TypeScript + Node 20: Synology NAS에서 단일 컨테이너로 실행됩니다.
- grammY: 웹훅 모드(webhook mode)로 연결된 경량 Telegram Bot 프레임워크입니다.
- Hono: 웹훅을 수신하고
/health에 응답하는 작은 HTTP 서버입니다. - Cloudflare tunnel:
bot.digikedai.com이 컨테이너 내부로 바로 라우팅됩니다. 공개 IP 주소도, 열린 포트도 없습니다. - LiteLLM: 실제 모델 앞에 위치한 자체 호스팅 게이트웨이(self-hosted gateway)입니다. 이를 통해 봇 코드를 수정하지 않고도 제공업체를 교체하거나 폴백(fallback)을 추가할 수 있습니다.
- PostgreSQL: 대화 및 사용자 메모리 저장소로, 저의 mem0 스택과 공유됩니다.
아키텍처 (The architecture)
Customer → Telegram/WhatsApp → Cloudflare tunnel → bot.digikedai.com
→ Hono POST /<secret>/webhook → grammY handler
→ PostgreSQL에 저장 → Agent.respond (시스템 프롬프트 + 검색된 카탈로그 사실 + 최근 20개 메시지)
...
설명할 가치가 있는 몇 가지 결정 사항이 있습니다:
Traefik 경유 없이 컨테이너로 Cloudflare tunnel을 연결하는 방식입니다. 제 홈랩(homelab)은 CGNAT 뒤에 있기 때문에, 터널 없이는 아무것도 공개적으로 접근할 수 없습니다. 웹훅 URL은 비밀 경로 세그먼트(/<secret>/webhook) 뒤에 존재하여, 호출자가 비밀번호를 알고 있을 때만 Telegram의 업데이트가 도착하도록 합니다. 이는 Telegram 자체 토큰 인증 위에 추가된 저렴한 1차 방어선입니다.
모델은 원시 모델 이름이 아닌 LiteLLM _alias_로 지정됩니다. 봇은 mem0-openai를 호출합니다. LiteLLM은 이를 실제 모델(그 뒤에 OpenRouter 폴백 포함)에 매핑합니다. 봇은 실제로 어떤 제공업체가 요청을 처리하고 있는지 알 필요가 없습니다. 저는 temperature: 0.4와 45s 타임아웃을 설정했습니다.
봇은 가격이나 내부 경로를 알지 못하면서도 카탈로그를 알고 있습니다. 빌드 시 생성기(build-time generator)는 단일 catalog_sku.csv 파일(진실의 원천, source of truth)을 TypeScript 모듈로 변환하여 봇이 가져다 쓰게 합니다. 이 모듈에는 SKU, 이름, 카테고리, 사이즈, 제품 URL만 포함되며, 그 외의 정보는 전혀 없습니다. 가격은 절대 내장되지 않습니다("현재 가격은 사이트를 확인하세요"). 또한 내부 리소스 경로는 봇 이미지에 배포되지 않습니다. 이는 번들(bundle)을 가볍게 유지하고 모델이 내부 구조를 유출하는 것을 방지합니다.
검색 (Retrieval): 봇이 카탈로그를 실제로 '아는' 방법
카탈로그 정보를 프롬프트에 단순히 채워 넣은 일반적인 LLM은 환각(hallucinate)을 일으킬 것입니다. 따라서 이 봇은 먼저 검색하고, 그 후에 답변합니다. 봇은 세 단계의 리트리버(retriever)를 사용합니다:
- SKU 토큰 일치 (SKU token match) —
/\b[A-Z]{2,}\d{2,}\b/gi정규식으로CZH01과 같은 정확한 SKU를 포착합니다. - 전체 쿼리 부분 문자열 (Whole-query substring) — 짧고 정확한 질의에 사용됩니다.
- 세그먼트별 일치 (Per-segment matching) — CJK 언어 구문(질문 조사인 有/吗/哪些 등을 제거)을 분할하고 영어 불용어(stopwords)를 필터링합니다.
상위 5개 매칭 결과가 시스템 프롬프트에 주입되는 "사실(facts)"이 되며, 모델은 실제로 검색한 내용만을 가지고 답변하도록 지시받고, 아무것도 찾지 못했을 때는 그렇게 말해야 합니다.
이는 보이는 것보다 훨씬 중요합니다. 고객이 "CZH01에 무료 버전이 있나요?"라고 질문할 때, 봇이 유료 SKU를 만났을 때 리트리버가 자동으로 그 무료 쌍둥이(free twin)를 첨부하고 이를 두 번째로 순위를 매기기 때문에 FREECZH01에 대한 정확한 답변을 받을 수 있습니다. 이 사소한 디테일 하나가 "아니요, 죄송합니다"라는 거절을 올바른 업셀링 기회로 바꿉니다.
무료 체험판 프로비저닝 — 인간 개입 없음 (no human in the loop)
제가 가장 자랑스러운 부분입니다. 봇은 단순히 무료 체험판에 대해 '말하는' 것이 아니라, 실제로 그것을 '발급(issue)'합니다. 고객은 세 가지 방법으로 이를 트리거할 수 있습니다: /trial 명령어 사용, "我要这个试用:digikedai.com/products/czh01"와 같은 자연어 메시지 전송, 또는 ?start=CZH01과 같은 딥 링크(deep link)를 통해 접근하는 것입니다.
n8n을 거치지 않고, 봇이 NocoDB에 직접 작성합니다. 고객 행(row)과 고객-제품 행(customer-product row)을 삽입하는 방식입니다. 제가 기존에 사용하던 웹훅(webhook)들이 이를 감지하여 실제 계정을 자동으로 프로비저닝(provision)하므로, 봇은 파일 서버를 건드리지 않습니다. 이 전체 과정은 **멱등성(idempotent)**을 갖추고 있습니다. 즉, 고객이 이미 계정을 가지고 있다면 중복 생성을 하는 대신 기존 계정을 재사용하고; 제품 삽입에 실패하면 고객 행을 롤백(roll back)하여 재시도 시 문제가 발생하지 않도록 합니다.
심지어 여러 계정 선택기까지 구현했습니다. 같은 Telegram 사용자가 여러 계정을 가지고 있을 경우, 봇이 이를 인라인 버튼(inline buttons)으로 나열하고 어떤 계정에 체험판을 연결할지 사용자에게 질문합니다.
하루를 날려버린 버그들
이것을 배포하는 과정은 순탄치 않았습니다. 특히 세 가지 디버깅 경험이 기억에 남습니다:
1. 무한 응답 루프(infinite reply loop). 서버가 웹훅 업데이트(webhook update)를 타임아웃 기간 내에 확인하지 못하면 Telegram이 이를 재전송합니다. 제 핸들러는 실행 시간이 길었기 때문에 (LLM 지연 시간), Telegram은 동일한 메시지를 반복해서 전송했고, 봇도 계속해서 응답했습니다. 해결책은 `onTimeout:
LLM 기반의 결정론적 흐름 (Deterministic flows on top of the LLM)
LLM은 개방형 질문에는 훌륭하지만, _상태(state)_를 다루는 데는 형편없습니다. 따라서 신뢰성이 필요한 대화 요소들—구매, 체험판 사용, 깊은 링크 등—은 프롬프트 엔지니어링이 아닌 **결정론적 상태 기계(deterministic state machines)**여야 합니다:
- 구매 의도 (Buy intent) (
order,want to buy,buy등)는 LLM에 도달하기 전에 가로채져 인라인 키보드(온라인 스토어 → 관리자 → 뒤로가기)를 구동하며, 절대 자유 형식의 답변을 생성하지 않습니다. - **깊은 링크 (Deep links)**는
?start=페이로드에 SKU를 담지만, Telegram에서는A-Z a-z 0-9 _ -만 허용합니다. 콜론(:) 하나가 이를 조용히 깨뜨립니다. 저는 이 경험을 통해 배우고 구분자를buy:SKU에서buy-SKU로 변경했습니다. - WhatsApp의 **비주제 메시지 (Off-topic messages)**는 모델이 신호(sentinel) 값 (
NO_REPLY)을 내보내게 하고, 에이전트는 이를 _"아무것도 보내지 않기(send nothing)"_로 변환합니다. 따라서 봇은 유용한 말을 할 것이 없을 때 장황하게 말하는 대신 침묵을 유지합니다.
다르게 할 점 (What I'd do differently)
- 시작 시
setMyCommands()추가. 명령어 목록은 코드로 정의되지만, 저는 이를 Telegram에 등록한 적이 없어 Bot API를 통해 수정하기 전까지 채팅 내 명령어 메뉴가 비어 있었습니다. 사소하지만 실제 고객 접점을 놓치는 결과를 초래했습니다. - 채널 어댑터 이전에 페이로드 계약(payload contract) 설계. WhatsApp의
m_text버그는 제가 계약 내용을 읽지 않은 커스터마이징 기능에 의존했기 때문에 발생했습니다. - 웹훅 타임아웃을 사후 고려가 아닌 일급 아키텍처로 취급. 처음부터 승인(acknowledgement) 창을 고려했다면 재전송 루프를 피할 수 있었을 것입니다.
결과 (The result)
이제 NAS의 컨테이너 하나가 두 개의 채널(Telegram + WhatsApp)에서 고객 질문에 답변하고, SKU 또는 자연어를 통해 올바른 카탈로그 항목을 검색하며, 엔드투엔드로 무료 체험 계정을 발급합니다. 이 시스템은 결정론적 흐름, 검색 기능, 페이로드 계약을 다루는 약 175개의 단위 테스트(unit tests)로 구성된 프롬프트 스위트와 함께 작동합니다. 소스 코드는 git.hoelee.com/hoelee/digikedai-bot에서 확인할 수 있습니다.
만약 LLM을 백엔드로 하는 자신만의 Telegram 봇을 구축한다면, 얻게 되는 교훈은 지루한 것입니다: 모델이 쉬운 부분입니다. 웹훅(webhook) 수명 주기, 페이로드 계약(payload contract), 그리고 프로비저닝의 아이뎀포턴시(idempotency)가 실제로 문제가 발생하는 곳입니다—이것들을 먼저 설계하세요.
비즈니스를 위해 이런 봇을 원하시나요?
저는 기업을 위한 맞춤형 Telegram/WhatsApp AI 봇, 웹사이트, 그리고 자체 호스팅 인프라를 구축합니다. 만약 이런 봇이 시간과 돈을 절약해 줄 수 있다면—혹은 저를 고용하고 싶으시다면—이야기를 나누고 싶습니다:
- 📱 WhatsApp: +60 12-797 2969
- 📧 Email: [email protected]
- 🌐 Website: hoelee.com
저는 blog.hoelee.com에서 자체 호스팅 인프라, DevOps, 그리고 그 이면에 있는 실패들에 대해 글을 씁니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기