단톡방 독서 인증을 봇이 알아서 기록·집계하게 만든 이야기. 잘못된 인증, 눈치 없이 끼어드는 봇, 매일 죽는 안드로이드 컨테이너까지 — 삽질과 매일열장 6월 운영 후기.
단톡방 독서 인증을 봇이 알아서 기록·집계하게 만든 이야기. 잘못된 인증, 눈치 없이 끼어드는 봇, 매일 죽는 안드로이드 컨테이너까지 — 삽질과 매일열장 6월 운영 후기.
Sangwoo Yang
@IGhost-P

예전에 친구들끼리 독서 스터디를 한창 열심히 했을때가 있었다…
예쩐부터 친구들이랑 스터디를 자주 했었는데, 취준까지는 개발 공부만 하고 있었지만, 취업하고 나니 그게 싫어서 퇴근하고는 코드 대신 책을 좀 읽고 싶었고, 그래서 매일 읽은 만큼 인증하는 스터디를 만들었었다. 근데 친구들끼리는 잘 되진 않았다.. (매번 나만 읽어서…)
그래서 사내 동호회에 들어가 책 읽는 사람들을 찾아다녔고, 그러다 매일 열장을 시작하게 되었었다

한동안은 좋았는데, 매일 열장을 하다 보니 관리할 인원이 너무 많아졌다. 마침 sipe 동아리 활동까지 한창 열심히 할 때라 시간이 나지 않았고.. 결국 스터디를 놓고 다시 혼자만 책을 읽으며 지냈다

그러다 올해 sipe 5기 기수에 책을 좋아하는 사람들이 많다는 걸 알게 됐다. 매일 열장을 다시 한번 열어보고 싶다는 생각이 들었고, 그렇게 고민을 하다가 용기내어 단톡방에 글을 올렸다

일단 사람을 구한다고 저질러버리고, 그 뒤에야 정신이 들어 소규모로만 진행한다고 했다.. 그래야 관리가 될 것 같아서 말이다
근데 갑자기 뇌리에 스친게, 인증 관리가 어려웠던 거였다면.. 그걸 그냥 ai한테 시키면 되는 거 아닌가..? 회사에서 ax 업무를 주구장창 하는데 이걸 못할 리가 없으니깐..
그래서 ai 챗봇이랑 홈서버로 만들면 금방 뚝딱 만들 수 있을 거라 생각했다.
일단 우리 집 홈서버에 상주하고 있는 내 ai 에이전트부터 소개하자면 calcifer라는 녀석이다. pi-ai를 래핑한 llm인데, 커스텀 기능을 많이 붙여놔서 api 호출로 llm 호출이랑 다양한 tool calling이 가능하다.

여기에 서버 db랑 fast api 서버를 결합하면, 계정만 있으면 어디서든 이 챗봇을 쓸 수 있다.
핵심 아이디어는 하나다. "단톡방 메시지를 입력으로 받는 데이터 파이프라인" 을 만드는 것.
그래서 만든 아키텍처 구조는 아래와 같다
flowchart TB
KT([카카오톡 단톡방])
subgraph AND[컨테이너형 안드로이드 · 예 redroid]
APP[카톡 앱]
BR[메시지 브리지 · 예 Iris류]
end
subgraph SRV[수신·처리 서버 · FastAPI]
WH[Webhook 수신]
BUNDLE[메시지 번들러]
GATE{게이트 · 멘션·명령 판별}
FAST[빠른 길 · 정규식 파서]
LLM[LLM 라우터 · tool calling]
DOM[도메인 처리 · 진도·메타 보강]
end
STATE[(로컬 상태 · SQLite)]
NOTION[(협업 DB · Notion류)]
KT --> APP --> BR -->|webhook| WH
WH --> BUNDLE --> GATE
GATE -->|명시적 명령| FAST
GATE -->|자유 자연어| LLM
GATE -->|잡담·비멘션| IGN[무시]
FAST --> DOM
LLM --> DOM
DOM <--> STATE
DOM --> NOTION
DOM -->|짧은 확인 메시지| BR메시지는 어떻게 받나? 서버 안에서 안드로이드를 통째로 컨테이너로 띄워 채팅방을 24시간 로그인 상태로 굴리고, 그 앱이 받은 메시지를 webhook으로 우리 서버에 흘려보낸다. 단톡방 입장에선 봇이 그냥 평범한 참여자 한 명처럼 보인다.
의도 분류는 왜 LLM으로? 사람들의 인증 형식이 너무 자유롭기 때문이다. 280p, 3장 끝!, 어제 못 읽어서 오늘 두 배 읽음 ㅋㅋ, 드디어 완독 🎉... 정규식으로는 감당이 안 된다. 그래서 명확한 슬래시 명령(/시작, /현황)은 정규식이 즉시 처리하고, 자유로운 문장은 LLM이 "이건 인증이야 / 명령이야 / 그냥 잡담이야"를 판단하게 했다.
기록은 두 군데. 사람이 보는 정면(노션 DB)과, 봇이 상태를 기억하는 뒷단(SQLite). "이 사람이 마지막으로 읽던 책은 뭐고 몇 페이지였는지" 같은 건 SQLite가 들고 있어서, 사용자가 책 제목을 매번 안 써도 알아서 이어 붙는다.\
flowchart LR
A[메시지 수신] --> B[번들링]
B --> C[의도 분류]
C -->|인증| D[인증 처리 · 진도 계산]
C -->|명령| CMD[명령 실행]
C -->|잡담| X[무시]
D --> E{저장 성공?}
CMD --> E
E -->|성공| F[응답 · 반영 완료]
E -->|실패| G[낙관적 ACK · 접수됨]
G --> Q[(재시도 큐)]
Q -.->|백그라운드 재시도| D단톡방 메시지가 챗봇을 거쳐 서버로 들어오면, calcifer가 이게 인증인지 잡담인지 판단한다. 인증이면 tool calling으로 db에 바로 기록하고, 잡담이면 그냥 흘려보낸다. 그렇게 쌓인 기록은 정해진 시각에 집계돼서 랭킹이나 리마인드로 다시 단톡방에 돌아온다. 예전에 내가 손으로 하던 걸, 이제 이 파이프라인이 알아서 도는 셈이다.
다만 ax를 제대로 하려면 여기서 고려할 게 꽤 많은데.. 그건 이 글에서 다루지 않겠다 (너무 길어 ㅠ🫠)
이렇게 만든 챗봇을 실제로 단톡방에서 굴려보면~

이런 식으로 잘 작동한다.
사실 참가자 입장에선 예전이랑 크게 다를 게 없다. 그냥 단톡방에 “오늘 3장까지 읽었어요” 하고 던지면 끝이다. 달라진 건, 그걸 이제 사람(=나)이 아니라 봇이 받아서 알아서 기록하고 집계한다는 거다. 형식이 자유로워도 calcifer가 해석해주니까 그냥 편하게 올리면 되고, 인증 방법 같은 자세한 건 안내 페이지에 정리해뒀다.
이렇게 다 만들어놨으니, 이제 사람들을 초대해서 손쉽게 인증 스터디를 이어나갈 수 있게 됐다.
봇이 세상에 있지도 않은 책을 하나 만들어낸 적이 있었는데, 누가 "앞에 인증한거" “다시 ” 이라고 올린 걸 『다시』라는 제목의 책으로 등록해버려서.. 한동안 아무도 안 읽는 유령 책이 랭킹에 올라와 있었다. 판단 기준을 손봐서 잡았다.
가끔은 notion api서버가 잠깐 죽어서, 인증이 제대로 가지 않는 경우도 있었는데,, 이벤트큐 방식을 도입해서, 낙관적 업데이트 이후 큐를 처리하는 방식으로 설계 했다. (게임 회사에서 좋아하는 방법)
또, 그냥 잡담으로 “브람스를 좋아하세요 그거 150p쯤 밖에 안해요” 이런 걸 올렸는데, 봇이 이걸 “150p 인증"으로 잡아서 기록해버릴려고 했던 적도 있었다... 인증이랑 잡담을 구분하는 걸 조여서 고쳤다.

봇이 얹혀 살던 환경(redroid)이 하루에 한 번씩 죽는 문제도 있었다. 아침마다 봇이 살아있나 확인하는 게 일이었고.. 결국 죽으면 알아서 되살아나게 해두고서야 발 뻗고 잤다. 해결 방법으로는 watchdog를 사용했다. 주기적으로 redroid를 폴링해서 상태를 확인하는 방식이다 (k8s 처럼..)
간단한 줄 알았던 챗봇인데도 예상치 못한 부분들이 많았고, 나는 그럴 때마다 매를 들 수밖에 없었다.. (미안해 ai야)

그렇게 매도 들고 유령 책도 잡다 보니, 시간이 순식간에 지나 6월이 마무리됐다.
한 달 동안 다 같이 149번을 인증했다. 합치면 8,560페이지, 36권. 혼자 올공 구석에서 읽던 몇 달 전을 생각하면 꽤 큰 숫자다..

마무리 날에는 wrapped도 만들어봤다. 각자 무슨 책을 며칠째 읽었고 몇 번 인증했는지 숫자로 정리해서 나눠줬는데, 다들 좋아해주셔서 다행이였다..

짧은 한 달이었지만 그 안에서 정말 많은 인증과 얘기가 오갔다. 그리고 이번 6월 기수의 마무리 정산날인 책트럭파티가 아직 남아있다!!


홈서버를 통해서 ai를 굴리다보니 말도 많고 탈고 많았지만.. 배운게 많았다..
자유로운 입력을 받기로 한 순간 LLM은 필수가 되지만, LLM만 믿으면 잡담에 끼어들고.. 그래서 앞에는 정규식(빠르고 싸다)을 깔아 비용을 아끼고, 뒤에는 결정론적 가드레일을 깔아 신뢰성을 메우는 샌드위치 구조가 필요했다. 작은 모델로도 실용적인 봇을 만든 비결은 결국 이거였다.
그리고 하나 더 — 재미로 시작한 사이드 프로젝트가 실제로 사람들이 매일 쓰는 물건이 되면, 별걸 다 신경 쓰게 된다. 새벽에 컨테이너가 죽는 것도, 봇이 눈치 없이 끼어드는 것도, 다 "사용자"가 생겼기 때문에 고민한 문제였다. 그 과정 자체가 제일 재밌었다.
마지마으로..한 달 굴려보니 자신감이 생겼다. 그래서 이번엔 인원을 15명까지 늘려서 7-8월에 다시 진행해보려 한다. 잘 되면 채팅방을 2그룹으로 나눠서, sipe 말고 다른 사람들도 참여할 수 있게 하는 것도 생각 중이다..
(만약 관심이 있다면 연락을 주세요)