카톡 독서모임을 봇에게 맡겼더니 생긴 일들
카톡 단톡방 독서 인증을 봇이 알아서 기록·집계하게 만든 이야기. "다시"라는 유령 책, 눈치 없이 끼어드는 봇, 매일 죽는 안드로이드 컨테이너까지 — 삽질과 5기 운영 후기.
TL;DR
카톡 단톡방 독서 인증을 봇이 알아서 기록·집계하게 만든 이야기. "다시"라는 유령 책, 눈치 없이 끼어드는 봇, 매일 죽는 안드로이드 컨테이너까지 — 삽질과 5기 운영 후기.
Sangwoo Yang
@IGhost-P
발단: "이거 누가 정리하냐"
독서 모임을 카톡 단톡방으로 운영해 본 사람은 안다. 처음 한두 주는 아름답다. 사람들이 "오늘 320p까지 읽었어요 📖" 하고 인증을 올리고, 서로 이모지를 박아주고, 분위기가 좋다.
문제는 그다음이다. 누군가는 그걸 정리해야 한다.
- 누가 며칠째 연속 인증(streak) 중인지
- 이번 주 완독한 사람은 몇 명인지
- 사흘째 소식이 없는 사람은 누구인지
이걸 매주 손으로 표에 옮겨 적다 보면, 어느 순간 "내가 독서를 하려고 모임을 만든 건지, 엑셀을 하려고 만든 건지" 헷갈리는 경지에 도달한다. 그래서 만들었다. 단톡방에 올라오는 인증 메시지를 봇이 알아서 읽고, 기록하고, 통계까지 내주는 시스템.
이름은 짓다 보니 "매일 열 장씩"이라는 모임 규칙에서 따와 매일열장이 됐다. 이 글은 그걸 만들면서 겪은, 웃기고 삽질스러운 이야기들의 모음이다. 딱딱한 아키텍처 문서는 따로 있으니 여기선 가볍게 후기만.
어떻게 동작하냐면
핵심 아이디어는 하나다. "단톡방 메시지를 입력으로 받는 데이터 파이프라인" 을 만드는 것.
챗봇을 만든다는 느낌보다는, 사람들이 아무렇게나 던지는 자연어를 받아서 구조화된 기록으로 바꾸는 공장을 짓는다는 느낌에 가깝다. 전체 흐름을 그려보면 이렇다.
카톡 단톡방
│ "오늘 320p까지 읽었어요 📖"
▼
[안드로이드 컨테이너 속 카톡] ──webhook──▶ [FastAPI 수신 서버]
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
슬래시 명령? 자유로운 자연어? 그냥 잡담?
(정규식으로 즉시 처리) (LLM이 의도를 분류) (조용히 무시)
│ │
└──────────────┬─────────────┘
▼
진도 계산 · 책 정보 자동 보강
▼
┌──────────────────┴──────────────────┐
▼ ▼
노션 DB (사람이 보는 곳) SQLite (봇의 기억)
│
▼
"✅ 인증 접수됐어요!" (다시 단톡방으로)
몇 가지만 풀어서 설명하면:
- 메시지는 어떻게 받나? 카톡은 일반 단톡방 메시지를 빼낼 공식 API가 없다. 그래서 서버 안에서 안드로이드를 통째로 컨테이너로 띄워 카톡 앱을 24시간 로그인 상태로 굴리고, 그 앱이 받은 메시지를 webhook으로 우리 서버에 흘려보낸다. 단톡방 입장에선 봇이 그냥 평범한 참여자 한 명처럼 보인다.
- 의도 분류는 왜 LLM으로? 사람들의 인증 형식이 너무 자유롭기 때문이다.
280p,3장 끝!,어제 못 읽어서 오늘 두 배 읽음 ㅋㅋ,드디어 완독 🎉... 정규식으로는 감당이 안 된다. 그래서 명확한 슬래시 명령(/시작,/현황)은 정규식이 즉시 처리하고, 자유로운 문장은 LLM이 "이건 인증이야 / 명령이야 / 그냥 잡담이야"를 판단하게 했다. - 기록은 두 군데. 사람이 보는 정면(노션 DB)과, 봇이 상태를 기억하는 뒷단(SQLite). "이 사람이 마지막으로 읽던 책은 뭐고 몇 페이지였는지" 같은 건 SQLite가 들고 있어서, 사용자가 책 제목을 매번 안 써도 알아서 이어 붙는다.
여기까지가 교과서적인 설명이고, 진짜 재밌는 건 이걸 실제 사람들이 쓰기 시작하면서 벌어진 일들이다.
삽질 모음집
1. "다시"라는 이름의 유령 책
어느 날 통계를 보는데 처음 보는 책이 하나 있었다. 제목이 "다시". 저자도 없고, 표지도 없고, 그냥 "다시".
추적해 보니 범인은 이랬다. 한 멤버가 인증을 올렸는데 봇 반응이 늦자, 실패한 줄 알고 "다시, 320p까지 읽었어" 라고 재전송한 것이다. 그러자 우리의 (작고 저렴한) LLM이 맨 앞의 "다시"를 아주 자신 있게 책 제목으로 인식해 버렸다. 그렇게 "다시"라는 유령 책이 태어났고, 심지어 봇의 "이 사람이 지금 읽는 책" 상태(sticky state)까지 오염시켜서, 그 사람의 다음 인증 몇 개가 줄줄이 "다시"에 붙는 연쇄 참사가 났다.
여기서 배운 것: 재전송 말머리("다시", "아까 그거" 등)는 LLM한테 맡기지 말고 순수 함수로 미리 걸러내자. 단, 함정이 있다. 세상에는 『다시 태어나도 우리』 같은 진짜 "다시"로 시작하는 책 제목이 존재한다. 그래서 "제목 전체가 노이즈 패턴일 때만" 무력화하도록 조심스럽게 처리해야 했다. LLM 앞뒤로 결정론적 가드레일을 까는 게 왜 중요한지 몸으로 배운 사건.
2. 봇이 잡담에 끼어들기 시작하다
LLM한테 의도 분류를 맡기면 생기는 부작용이 있다. 작은 모델일수록 아무 잡담이나 "이건 질문이군!" 하고 오해한다는 것.
한번은 누가 단톡방에 "150p 읽다가 졸려서 죽는 줄 ㅋㅋㅋ" 라고 그냥 신세 한탄을 했는데, 봇이 이걸 진지한 인증 겸 질문으로 받아들이고 성실하게 반응해 버렸다. 사람들끼리 농담 주고받는데 봇이 자꾸 끼어들면, 그건 단톡방 분위기를 깨는 가장 빠른 길이다. AI 봇의 가장 큰 미덕은 눈치라는 걸 그때 알았다.
해결책은 멘션 게이트. 인증이나 명령처럼 봇의 본업에 해당하는 건 멘션 없이도 처리하되, "질문에 답하기" 같은 대화형 반응은 봇을 명시적으로 부를 때(@멘션)만 하도록 했다. 그리고 프롬프트에도 "애매하면 무조건 무시해라"를 못 박았다. 그 뒤로 봇은 "필요할 때만 말하는" 과묵한 캐릭터가 됐다.
3. redroid가 매일 아침 죽어 있는 문제
가장 오래 괴롭힌 건 정작 AI가 아니라 인프라였다. 카톡을 굴리는 안드로이드 컨테이너(redroid)가 하루가 멀다 하고 죽어 있었다. 아침에 일어나면 밤사이 인증들이 통째로 유실돼 있는 참사가 반복됐다.
처음엔 원인도 몰라서 매일 아침 수동으로 컨테이너를 재시작하는, 아주 원시적인 인간 watchdog으로 살았다. 결국 진짜 watchdog을 붙였다. 주기적으로 "너 살아있니?"를 확인하고, 죽었으면 알아서 되살리고, systemd에도 Restart=on-failure를 걸어서 "뭐가 죽든 알아서 다시 뜬다" 를 기본값으로 만들었다. 비공식 경로로 메시지를 받는 시스템에서 안정성은 "있으면 좋은 것"이 아니라 처음부터 설계에 박혀 있어야 하는 것이라는 걸, 잠을 여러 번 설치고 나서 깨달았다.
4. "접수는 됐는데 반영이 안 됐어요" 문제
노션 API는 사람 많을 때 종종 삐끗한다(429/503 같은 걸 뱉는다). 초기엔 이때 사용자한테 아무 응답도 안 갔다. 그래서 인증을 올린 사람은 "된 거야 만 거야?" 하고 불안해했다. 최악의 UX.
그래서 "받았다는 확인"과 "완료됐다는 확인"을 분리했다. 일단 즉시 ✅ 인증 접수됐어요! 반영 중이에요 📝로 안심시키고, 실제 저장은 백그라운드 재시도 큐가 끝까지 책임진다. 노션이 회복되면 조용히 반영되고, 정 안 되면 나중에 ✅ 반영됐어요!가 뜬다. 사용자 경험은 동기적으로, 실제 작업은 비동기적으로 — 외부 의존이 불안정할 때의 황금률이다.
5기 성적표
이렇게 굴린 봇으로 5기를 운영했다. 결과는 이랬다.
| 항목 | 숫자 |
|---|---|
| 참여 인원 | 11명 |
| 총 인증 횟수 | 149회 |
| 함께 읽은 페이지 | 8,560p |
숫자만 보면 그냥 표지만, 저 8,560페이지 안에는 새벽에 졸면서 읽은 페이지도, "오늘까지만 봐주세요" 하고 벼락치기로 채운 페이지도 다 들어있다. 봇이 매일 조용히 기록해 준 덕분에, 시즌이 끝날 때 그걸 전부 되돌아볼 수 있었다.
그래서 마지막엔 Wrapped(스포티파이 연말결산 같은 거)를 만들었다. "당신의 최애 장르", "가장 길게 이어간 연속 인증", "완독한 책들"을 사람별로 정리해서 돌렸더니 반응이 제일 좋았다. 사실 이게 봇을 만든 진짜 이유이기도 하다 — 매일의 기록이 쌓여야 이런 결산이 나온다. 손으로 엑셀 치던 시절엔 절대 못 했을 일이다.
배운 것들, 그리고 다음
만들면서 얻은 교훈을 한 줄로 요약하면 이거다.
LLM은 만능 해결사가 아니라, 파이프라인의 한 레이어일 뿐이다.
자유로운 입력을 받기로 한 순간 LLM은 필수가 되지만, LLM만 믿으면 잡담에 끼어들고 "다시"라는 유령 책을 만든다. 그래서 앞에는 정규식(빠르고 싸다)을 깔아 비용을 아끼고, 뒤에는 결정론적 가드레일을 깔아 신뢰성을 메우는 샌드위치 구조가 필요했다. 작은 모델로도 실용적인 봇을 만든 비결은 결국 이거였다.
그리고 하나 더 — 재미로 시작한 사이드 프로젝트가 실제로 사람들이 매일 쓰는 물건이 되면, 별걸 다 신경 쓰게 된다. 새벽에 컨테이너가 죽는 것도, 봇이 눈치 없이 끼어드는 것도, 다 "사용자"가 생겼기 때문에 고민한 문제였다. 그 과정 자체가 제일 재밌었다.
다음 계획은 대충 이렇다.
- 인증 경로 다양화: 지금은 카톡 텍스트만. 사진 인증(책 표지 OCR)도 붙여보고 싶다.
- Wrapped 자동화: 시즌 끝날 때 한 방에 뽑히도록.
- 모임 운영 대시보드: 운영자가 노션 안 켜도 한눈에 보게.
혹시 자기 단톡방을 자동화하고 싶은 사람이 있다면, 생각보다 별거 아니면서 생각보다 삽질이 많다는 것만 기억하면 된다. 그래도 시즌 결산에서 사람들이 "오 내가 이만큼 읽었어?" 하고 놀라는 걸 보면, 그 삽질값은 충분히 한다. 🌱