카카오톡 대화 분석 서비스를 하루 만에 배포한 이야기
들어가며
“ㅋ 몇 개 치는지가 성격이라는 거, 알고 계셨나요?”
카카오톡 대화 내보내기 파일을 넣으면, AI가 참여자의 성격과 관계를 분석해주는 서비스를 만들고 싶었습니다. Tokka(톡까)는 3월 11일 하루, 21개 커밋과 함께 프로덕션에 배포되었습니다. Codex와 Claude Code를 동시에 사용하면서요.
이 글은 “이렇게 만들었다”가 아니라 “만들면서 뭘 배웠는가”에 대한 이야기입니다. 결론부터 말하면, AI 제품에서 진짜 어려운 건 AI가 아니었습니다.
30초 버전
카카오톡의 “대화 내보내기” 텍스트 파일에서 생각보다 많은 걸 뽑아낼 수 있습니다:
- 통계: 메시지 빈도, 응답 시간, ㅋ 패턴, 활동 시간대
- AI 페르소나: 성격, MBTI 추정, 관심사, 커뮤니케이션 스타일
- 관계: 관계 온도, 공감 포인트, 갈등 패턴, 인사이드 조크
- SNS 카드: 공유 가능한 트위터/인스타 콘텐츠, “나의 카톡 DNA” 카드
아키텍처는 3분 만에 결정했습니다. 이미 운영 중인 인프라가 있었으니까요.
[사용자] → tokka.jiun.dev (React SPA) ↓ FastAPI (Python) ← Gemini API ↓ Redis (잡 상태 저장) ↓[배포] GitHub → Gitea Actions → registry.jiun.dev → ArgoCD → K8s새로 만들 건 파서와 분석 로직뿐. 나머지는 전부 기존 파이프라인을 재사용했습니다.
Surprise 1: 전체 개발 시간의 절반은 AI가 아니라 텍스트 파싱이었다
Gemini API를 호출하는 코드는 하루의 20%도 안 걸렸습니다. 나머지는 전부 “AI 바깥”의 문제였습니다.
카카오톡 내보내기 파일은 정형 데이터가 아닙니다. 플랫폼마다 인코딩이 다르고(UTF-8 vs CP949), OS마다 포맷이 다르고(iOS: 쉼표 구분, Windows: 대괄호 구분, CSV: 또 다른 형식), 줄바꿈이 포함된 메시지는 다음 메시지와 경계가 모호합니다. ZIP 안에 .txt가 여러 개 들어있기도 합니다.
AI한테 “이 대화를 분석해줘”라고 던지기 전에, 그 대화를 구조화된 데이터로 만드는 과정이 훨씬 까다로웠습니다. 파서 코드가 413줄인 데는 이유가 있습니다.
그리고 통계 분석(응답 시간, 대화 주도, ㅋ 패턴 분류, 읽씹 감지 등) 405줄도 전부 순수 Python입니다. LLM이 필요 없는 영역이었죠. AI는 이 정형 데이터 위에 “성격”과 “관계”라는 해석을 얹는 마지막 단계일 뿐이었습니다.
배운 것: AI 제품의 복잡도는 모델 호출이 아니라 입력 데이터를 정제하는 과정에 숨어 있습니다. “데이터가 깨끗하면 프롬프트는 간단해진다”는 말이 과장이 아닙니다.
Surprise 2: LLM은 프롬프트를 무시한다 — 특히 출력이 길어질 때
1:1 대화에서는 완벽했습니다. ## 한줄 소개, ## 성격 프로필 같은 마크다운 헤딩을 프롬프트에 지정하면, Gemini가 정확히 그 형식으로 응답했죠.
그런데 16명 그룹채팅 28만 건을 넣는 순간 모든 게 깨졌습니다.
# 프롬프트에서 요청한 형식# [홍길동]의 카톡 페르소나## 한줄 소개분석적이고 감성적인 IT 전문가.
# Gemini가 실제로 보낸 형식### 1. 홍길동: "데이터로 말하는 추진력"* **한줄 소개**: 냉철한 분석과 빠른 실행력을 겸비한 행동 대장.출력이 길어지면 LLM은 자기가 더 효율적이라고 판단한 형식으로 바꿔버립니다. 프론트엔드 파서는 ## 한줄 소개라는 헤딩을 찾고 있었으니, 그룹채팅 결과는 전부 파싱 실패. 사용자에게는 fallback 메시지만 보였습니다.
더 재미있는 건 “참여자 누락” 문제였습니다. 16명을 분석하라고 했는데, Gemini가 “핵심 멤버 위주”라며 4명만 리포트를 썼습니다. 메시지 4만 건을 보낸 사람이 빠져있었죠.
73,771 msgs ✓ AI 안태우44,845 msgs ✗ 빠짐 박원 ← 2위인데 빠짐41,243 msgs ✓ AI 이종서35,606 msgs ✗ 빠짐 배지운 ← 4위인데 빠짐해결: 두 가지 층위에서 대응했습니다.
첫째, 프론트엔드 파서를 유연하게. 헤딩 매치 실패 시 bold-label 블록(**Label**: content)으로 폴백하는 2단계 탐색을 추가했습니다. LLM의 출력 형식을 통제하기보다, 어떤 형식이 오더라도 파싱할 수 있는 방어적 파서를 만든 거죠.
둘째, 누락 참여자 감지 + 보충 호출. 합성된 리포트에서 빠진 참여자를 찾아내고, 활동량이 일정 이상인 사람만 골라 추가 Gemini 호출을 합니다.
배운 것: LLM을 프로덕션에 쓸 때, “원하는 출력이 안 나오면?”은 if가 아니라 when입니다. 입력 프롬프트를 완벽하게 만드는 것보다, 불완전한 출력을 우아하게 처리하는 파서를 만드는 게 더 중요합니다.
Surprise 3: 사용자는 분석 정확도보다 “공유 가능한 카드”에 열광했다
가장 공들인 건 3-패스 청크 분석 파이프라인이었습니다. 500~800개 메시지 청크 분석 → 인물별 프로필 통합 → SNS 콘텐츠 생성. 이 파이프라인을 설계하고 프롬프트를 튜닝하는 데 상당한 시간을 썼습니다.
그런데 실제 사용자 반응은 예상과 달랐습니다. 페르소나 분석의 정확도에 감탄하는 사람보다, MBTI 카드를 캡처해서 SNS에 올리는 사람이 압도적으로 많았습니다.
돌이켜보면 당연합니다. “나의 카톡 성격은 ENFP”라는 한 줄은 공유 가능하지만, “당신의 커뮤니케이션 스타일은 감정 표현에 즉시적이며…”라는 세 문단은 공유할 수 없습니다. 사용자에게 AI 분석의 가치는 정확도가 아니라 공유했을 때 대화가 시작되는가였습니다.
이 깨달음 이후 OG 이미지 생성(Pillow 기반 동적 이미지), 카드 내보내기(html2canvas), SNS 최적화에 더 투자했습니다. 결과적으로 “AI 분석”보다 “공유 UX”가 서비스의 핵심 가치가 되었습니다.
배운 것: AI 제품의 가치는 모델의 출력 품질이 아니라, 그 출력이 사용자의 일상에서 어떻게 소비되는가에 달려 있습니다. 정교한 5페이지 분석보다 공유 가능한 카드 한 장이 더 큰 임팩트를 만들었습니다.
가장 재미있던 순간들
세션 로그에서 발견한 재미있는 대화들:
개인정보 걱정:
“저희 서비스는 궁극적으로 개인정보를 처리하고 있는데 개인정보 처리에 대해 동의를 받아야할것같습니다. 저는 개인사업자가 별도로 없으며 장난감 프로젝트입니다”
장난감 프로젝트인데 법적 의무를 고민하는 개발자의 성실함(?)이 기록에 남았습니다.
실시간 디버깅 연쇄:
09:23 - “persona, 관계, SNS 페이지가 아무것도 보이지 않습니다” 09:24 - “AI가 실패했다면 실패했다고 나타나야합니다” 09:25 - “왜 빈화면이 나왔는지 분석하세요”
배포하고 → 확인하고 → 안 되고 → 고치고. 이 사이클이 2분 간격으로 반복됩니다.
직설적 피드백:
“알아서 좀 제대로 찾아서 문제를 해결하는걸 우선으로 진행하세요” “문제의 근본원인을 찾아서 해결해야합니다”
AI 에이전트에게 화가 나서 직설적으로 말하는 순간. 프롬프트도 감정이 담깁니다.
숫자로 보는 하루
| 지표 | 수치 |
|---|---|
| 총 커밋 수 | 21개 (Day 1) + 10개 (Day 2) |
| 파서 코드 | 413줄 |
| 통계 분석 코드 | 405줄 |
| AI 분석 코드 | 393줄 → 470줄 (보충 호출 추가) |
| AI 리포트 파서 | 457줄 (그룹채팅 포맷 지원) |
| 프론트엔드 | React + TypeScript |
| Gemini API 키 | 5개 (free, round-robin) + 1개 (paid, fallback) |
| “커밋 푸시” 횟수 | 합산 10회 이상 |
| 최대 테스트 대화량 | 289,712건 (16명 그룹채팅) |
배포 이후: 안정화 작업
하루 만에 배포했다는 건, 하루 만에 만든 기술 부채도 있다는 뜻입니다. 이후 며칠간 안정화 작업을 진행했습니다.
해결된 과제들
AI 리포트 파싱: 그룹채팅 형식 미지원→ bold-label 폴백 파서로 해결그룹채팅 일부 참여자 누락→ 보충 호출로 해결동시 분석 시 크래시→ 큐 기반 순차 처리로 전환 (웹/이메일 각각 bounded queue + worker thread)API 키 전체 소진 시 서비스 중단→ Free/Paid 2단계 fallback 구조로 해결Sealed Secrets dev/prod 불일치→ dev/prod 모두 SealedSecret 사용으로 통일이메일 실패 시 어떤 파일인지 모름→ 에러 메일에 파일명 포함 (XSS 방지 처리)공개설정 변경 불가 (내 분석)→ JWT 소유자 인증 시 비밀번호 없이 토글 가능Graceful shutdown 미구현→ non-daemon worker threads +threading.Event시그널 + sentinel 패턴 + SIGTERM 안전망 핸들러로 구현.terminationGracePeriodSeconds: 600설정으로 K8s와 연동. 배포 시 진행 중 분석을 안전하게 완료하거나 에러 처리OG 이미지 성능/보안→ Pillow 기반 동적 OG 이미지 생성, 폰트 캐싱(lru_cache), 서버사이드 이미지 캐싱,is_public접근 제어, gradient 최적화데모 페이지 불완전→ 샘플 데이터를 실제 분석 결과 포맷에 맞춰 전면 재작성. 페르소나/관계/SNS 탭 모두 fallback 없이 정상 렌더링
아직 남은 과제
- 싱글 팟 문제: nginx, uvicorn, email_worker가 하나의 팟에서 동작. 하나가 죽으면 전부 죽음
- 레이트 리미터 상태: 인메모리 레이트 리미터는 팟 재시작 시 초기화됨
- Prometheus 모니터링 고도화: LLM 호출 성공/실패/쿼타 메트릭은 수집 중이나, 대시보드와 알럿은 미구성
마치며
하루 만에 서비스를 배포하며 확인한 건, “AI 제품”에서 AI의 비중이 생각보다 작다는 것입니다.
개발 시간의 절반은 카카오톡 파일 파싱에, 가장 까다로운 엔지니어링은 LLM의 불안정한 출력을 안정적으로 처리하는 파서에, 그리고 가장 큰 사용자 임팩트는 AI 분석이 아닌 공유 카드 한 장에서 나왔습니다. 모델은 점점 좋아지지만, 그 모델을 제품으로 만드는 과정의 복잡도는 줄어들지 않습니다.
물론 “하루 만에”의 이면에는 몇 달에 걸쳐 구축한 K8s 클러스터, GitOps 파이프라인, 도메인 설정 같은 인프라가 있습니다. git push만으로 배포가 완료되는 환경을 이미 갖추고 있었기 때문에 가능했던 일이죠. 서비스 로직은 하루 만에 만들었지만, 그것을 배포하고 운영할 수 있는 기반은 하루 만에 만들어지지 않았습니다.
그리고 “배포”가 끝이 아니라는 것도 배웠습니다. 1:1 대화에서 완벽하게 동작하던 모든 것이 16명 그룹채팅에서 깨졌습니다. 서비스를 만드는 것보다 서비스를 살아있게 유지하는 게 더 어렵다는 걸, 하루 만에 체감했습니다.
그래도 확실한 건, 아이디어에서 프로덕션까지의 거리가 점점 줄어들고 있다는 점입니다. “이거 만들면 재밌겠다”라는 생각이 들면, 다음 날 아침에 실제로 돌아가는 서비스가 있을 수 있는 시대입니다.
카카오톡 대화 파일이 있으시다면, tokka.jiun.dev에서 직접 분석해보세요. ㅋ의 개수가 당신의 성격을 말해줄 겁니다.
Comments
Loading comments...