카카오톡 대화 분석 서비스를 하루 만에 배포한 이야기

ㅋ 패턴으로 성격을 분석해주는 Tokka를 3월 11일 하루 만에 만들어 배포했습니다. 그런데 만들고 보니, AI 제품에서 진짜 어려운 건 AI가 아니었습니다.

카카오톡 대화 분석 서비스를 하루 만에 배포한 이야기

“ㅋ 몇 개 치는지가 성격이라는 거, 알고 계셨나요?”

카카오톡의 “대화 내보내기” 파일을 넣으면 AI가 참여자의 성격과 관계를 분석해주는 서비스, Tokka(톡까)를 만들었습니다. 3월 11일 오전에 시작해서 14시 43분에 첫 커밋, 그날 저녁에는 실서비스가 돌아가고 있었습니다. 첫날 커밋만 40개입니다.

사실 처음 이름은 톡까가 아니었습니다. 저장소 이름은 kakao-persona였고 도메인도 persona.jiun.dev로 잡아뒀는데, 시작 한 시간쯤 뒤에 tokka.jiun.dev로 갈아탔습니다. “톡을 까본다”는 어감이 훨씬 서비스다웠거든요. 15시 37분 커밋에 그 흔적이 남아 있습니다.

톡까 - 카카오톡 대화 분석기 브랜드 카드

하루 만에 배포가 가능했던 건 제가 빨라서가 아니라, 고민할 게 없었기 때문입니다. 이미 운영 중인 K8s 클러스터와 GitOps 파이프라인이 있어서, 새로 만들 건 파서와 분석 로직뿐이었습니다.

[사용자] → tokka.jiun.dev (React SPA)
FastAPI (Python) ← Gemini API
Redis (잡 상태 저장)
[배포] Gitea Actions → 사설 레지스트리 → ArgoCD → K8s

git push가 곧 배포인 환경이라, 이날 하루 종일 “고치고 → 푸시하고 → 프로덕션에서 확인하는” 사이클을 돌렸습니다. 그리고 이 글은 그 하루(와 그 뒤 며칠)에 대한 이야기입니다. 결론부터 말하면, AI 제품에서 진짜 어려운 건 AI가 아니었습니다.

Tokka 랜딩 페이지 - 카톡 내보내기 파일을 올리거나 이메일로 보내면 분석이 시작된다

절반은 텍스트 파싱이었다

Gemini를 호출하는 코드보다 카카오톡 파일을 파싱하는 코드가 먼저, 그리고 더 오래 저를 붙잡았습니다.

카카오톡 내보내기 파일은 정형 데이터가 아닙니다. 인코딩이 UTF-8일 수도 CP949일 수도 있고, iOS는 쉼표 구분, Windows는 대괄호 구분, CSV는 또 다른 형식입니다. 줄바꿈이 포함된 메시지는 다음 메시지와 경계가 모호하고, ZIP 안에 .txt가 여러 개 들어있기도 합니다. 첫날 밤에도 CP949 인코딩 수정(21시 19분)과 Windows 포맷 지원(21시 28분) 커밋이 이어졌습니다. 파서는 이틀째가 끝날 무렵 413줄이 되어 있었습니다.

부끄러운 사실 하나. 한국에서 제일 많이 쓰는 안드로이드의 내보내기 포맷은 나흘 뒤인 3월 15일에야 지원했습니다. 손에 있던 테스트 파일이 전부 iOS 포맷이었던 탓입니다.

통계 분석(응답 시간, 대화 주도, ㅋ 패턴 분류, 읽씹 감지)도 400줄 넘게 전부 순수 Python입니다. LLM이 필요 없는 영역이죠. AI는 이 정형 데이터 위에 “성격”과 “관계”라는 해석을 얹는 마지막 단계일 뿐이었습니다. AI 제품의 복잡도는 모델 호출이 아니라 입력 데이터를 정제하는 과정에 숨어 있습니다.

Tokka 분석 대시보드 데모 - 메시지 통계, 시간대별 대화량, 대화 스타일 레이더 차트 (가상의 샘플 데이터)

LLM은 출력이 길어지면 프롬프트를 무시한다

1:1 대화에서는 완벽했습니다. ## 한줄 소개, ## 성격 프로필 같은 마크다운 헤딩을 프롬프트에 지정하면 Gemini가 정확히 그 형식으로 응답했습니다.

그런데 참여자가 많은 그룹채팅을 넣는 순간 깨졌습니다. 출력이 길어지면 LLM은 자기가 더 효율적이라고 판단한 형식으로 바꿔버립니다. ## 한줄 소개 대신 **한줄 소개**: ... 같은 bold-label로, 헤딩 구조 대신 번호 목록으로. 프론트엔드 파서는 지정한 헤딩을 찾고 있었으니 그룹채팅 결과는 전부 파싱 실패, 사용자에게는 fallback 메시지만 보였습니다.

이 문제와의 싸움은 커밋 로그에 고스란히 남아 있습니다. 첫날 20시 12분에 이미 “Make AI report parsing tolerant to format drift”라는 커밋이 있고, 이틀째에는 bold-label과 이름-헤딩 형식 대응(14시 16분), 그룹채팅 형식 대응(19시 01분)이 이어집니다. 결국 헤딩 매치에 실패하면 bold-label 블록으로 폴백하는 2단계 탐색 파서가 됐습니다. LLM의 출력을 통제하려 하기보다, 어떤 형식이 와도 받아내는 방어적 파서를 만든 겁니다. 이 파서가 486줄인데, 카카오톡 파일을 파싱하는 코드보다 AI의 출력을 파싱하는 코드가 더 길어졌습니다.

형식만 문제가 아니었습니다. 참여자 전원을 분석하라고 해도 Gemini가 “핵심 멤버 위주”로 일부만 리포트를 쓰는 일이 있었습니다. 메시지 수 상위권인 사람이 리포트에서 빠져 있기도 했죠. 그래서 이틀째 저녁, 합성된 리포트에서 빠진 참여자를 감지해 활동량이 일정 이상인 사람만 추가 호출로 보충하는 로직을 넣었습니다(20시 37분). 이 작업으로 AI 분석 모듈은 392줄에서 503줄이 됐는데, 늘어난 코드는 전부 “모델이 시킨 대로 안 했을 때”의 처리입니다. LLM을 프로덕션에 쓸 때 “원하는 출력이 안 나오면?”은 if가 아니라 when입니다.

무료 쿼터와의 두더지 잡기

첫날 밤 커밋 로그에는 다른 종류의 싸움도 보입니다. Gemini 무료 티어의 분당 요청 제한(RPM)입니다.

17시 34분에 flash-lite 모델들을 라운드로빈으로 돌리기 시작했고, 21시 53분에 쿼터 에러 재시도 백오프(30초/60초/90초), 23시 24분에 API 호출 사이 5초 딜레이, 23시 57분 — 첫날의 마지막 커밋 — 에 그 딜레이를 15초로 늘렸습니다. 이틀째에는 모델이 아니라 API 키를 라운드로빈으로 돌리게 했고, 나흘 뒤에는 무료 키가 전부 소진되면 유료 키로 넘어가는 2단계 폴백까지 갔습니다. 무료 쿼터로 서비스를 버티려는 자의 처절한 기록입니다.

하루의 실제 질감

이날 Codex와 Claude Code를 함께 썼는데, Codex 세션 로그에 남은 제 메시지들이 이날의 질감을 제일 잘 보여줍니다. 배포 직후인 18시 23분부터 8분 사이에 이렇게 보냈습니다.

18:23 - “현재 persona, 관계, sns 페이지가 아무것도 보이지않습니다. 문제를 해결하세요” 18:28 - “ai가 실패했다면 실패했다고 나타나야합니다” 18:31 - “왜 빈화면이 나왔는지 분석하세요”

배포하고 → 확인하고 → 안 되고 → 고치라고 하고. 같은 세션에서 한 시간 동안 “commit push”라는 메시지만 다섯 번 보냈습니다. 프로덕션 에러 메시지를 그대로 붙여넣으면 에이전트가 고치고, 푸시하면 몇 분 뒤 반영되는 사이클이 실제로 돌아갔습니다.

그 와중에 이런 고민도 로그에 남아 있습니다.

“저희 서비스는 궁극적으로 개인정보를 처리하고있는데 개인정보 처리에 대해 동의를 받아야할것같습니다. 어떻게하는게좋을까요? 저희는 대한민국 법의 적용을 받고 저는 개인사업자가 별도로없으며 장난감 프로젝트입니다”

장난감 프로젝트인데 법적 의무부터 고민하는 게 우스워 보일 수 있지만, 남의 사적인 대화 전체를 받는 서비스라 그럴 수밖에 없었습니다. 첫날에 개인 대화로 만들었던 데모 데이터를 “Claude vs Codex”라는 가상의 대화로 전부 교체했고(16시 05분), 코드와 문서에 남은 실명을 걷어냈고(16시 09분), 사흘째에는 데이터 처리 방침 문서화와 삭제 요청 절차를 붙였습니다. 위에 있는 대시보드 스크린샷이 실제 대화가 아닌 이유이기도 합니다.

첫날 16시 30분에 AdSense 광고 4개를 먼저 붙인 커밋도 있습니다. 서비스가 제대로 돌기도 전에요. 우선순위가 대단히 건강했다고는 못 하겠습니다.

정확도보다 공유 카드

가장 공들인 건 청크 분석 → 인물별 프로필 통합 → SNS 콘텐츠 생성으로 이어지는 분석 파이프라인이었습니다. 그런데 배포 이후 커밋 로그를 보면 손이 간 곳은 다른 데였습니다. 사흘째 밤에 MBTI 공유 카드와 SNS 카드 이미지 내보내기를 붙였고, 3월 15일에는 카톡으로 링크를 공유했을 때 보이는 OG 이미지를 Pillow로 동적 생성하게 했고, 5월에는 아예 응답속도 배틀, MBTI 퀴즈, 인스타 스토리 템플릿 같은 공유 전용 기능만 몰아서 추가했습니다.

돌이켜보면 당연합니다. “나의 카톡 성격은 ENFP”라는 카드 한 장은 캡처해서 단톡방에 던질 수 있지만, “당신의 커뮤니케이션 스타일은 감정 표현에 즉시적이며…”로 시작하는 세 문단은 아무도 옮기지 않습니다. 분석의 정확도를 높이는 커밋보다 결과를 공유 가능하게 만드는 커밋이 훨씬 많았다는 게, 이 서비스의 가치가 어디 있었는지에 대한 제 나름의 답입니다.

배포 이후: 하루치 기술 부채 갚기

하루 만에 배포했다는 건 하루 만에 만든 기술 부채가 있다는 뜻입니다. 이후 열흘 정도의 커밋 로그는 그 청산 기록입니다.

이틀째 새벽에 잡 상태를 인메모리에서 Redis로 옮겼는데(배포할 때마다 분석 결과가 날아갔으니까요), 일주일 뒤에는 그마저도 PostgreSQL로 다시 옮겼습니다. 3월 15일 하루에만 커밋이 36개인데, 동시 분석 요청을 거절하는 대신 큐에 쌓는 worker 구조, 배포 중에도 진행 중 분석을 안전하게 끝내는 graceful shutdown, 무료/유료 API 키 분리가 다 이날입니다. 3월 19일에는 rolling update 설정을 고쳐 무중단 배포를 만들었고, rate limit이 실제 클라이언트 IP 대신 내부 프록시 IP를 세고 있던 버그도 잡았습니다. 3월 20일, 파드 재시작으로 날아간 분석 카운터를 복구하는 커밋에 적힌 숫자는 1,340이었습니다. 열흘간 그만큼의 대화 파일이 지나갔다는 뜻입니다.

물론 남은 것도 있습니다. nginx, uvicorn, email worker가 한 팟에 살아서 하나가 죽으면 다 죽는 구조는 그대로고, 인메모리 rate limiter는 팟이 재시작하면 초기화되고, LLM 호출 메트릭은 수집만 하고 대시보드는 아직 없습니다.

마치며

하루 만에 서비스를 배포하며 확인한 건, “AI 제품”에서 AI의 비중이 생각보다 작다는 것입니다. 개발 시간의 절반은 카카오톡 파일 파싱에 들어갔고, 가장 까다로운 엔지니어링은 LLM의 불안정한 출력을 받아내는 486줄짜리 파서였고, 배포 이후의 투자는 대부분 공유 카드로 향했습니다. 모델은 점점 좋아지지만, 모델을 제품으로 만드는 과정의 복잡도는 별로 줄지 않았습니다.

그리고 “하루 만에”의 이면에는 몇 달에 걸쳐 만들어 둔 클러스터와 배포 파이프라인이 있습니다. 서비스 로직은 하루 만에 나왔지만, 그걸 저녁에 바로 프로덕션에 올리고 다음 날부터 고쳐나갈 수 있는 기반은 하루 만에 만들어진 게 아닙니다.

카카오톡 대화 파일이 있으시다면 tokka.jiun.dev에서 직접 분석해보세요. ㅋ의 개수가 당신의 성격을 말해줄 겁니다.

Comments

Loading comments...