WhatsApp이 내 웹 앱을 이긴 이유: 벡터 저장소 없는 EspaLuz의 2계층 메모리
요약
웹 앱 기반 스페인어 학습 서비스의 실패를 분석하고, WhatsApp 에이전트로 피벗하며 구축한 2계층 메모리 아키텍처를 소개합니다. 벡터 저장소 없이 인메모리와 디스크 기반의 저비용·고효율 메모리 관리 방식을 다룹니다.
핵심 포인트
- 사용자 경험 측면에서 새로운 앱 설치보다 기존 플랫폼(WhatsApp) 활용이 마찰을 줄임
- 비용 절감을 위해 Pinecone 같은 유료 벡터 저장소 대신 2계층 메모리 방식 채택
- Layer 1: Python 딕셔너리를 활용한 인메모리 기반의 단기 컨텍스트 윈도우 구현
- 상태 비저장(Stateless) API 환경에서의 효율적인 대화 문맥 유지 전략
원래 AIdeazz에 게시되었습니다 — 정식 링크와 함께 이곳에 교차 게시되었습니다.
스페인어 학습을 위한 나의 웹 앱, EspaLuz는 실패했습니다. 깔끔한 UI, 맞춤형으로 구축된 채팅 인터페이스, 그리고 첫 달에 100명의 가입자를 끌어모은 무료 티어를 갖추고 있었죠. 하지만 유료 결제로 전환된 사례는 0건이었습니다. 반면, 주말 동안 해킹하듯 만든 나의 WhatsApp 에이전트는 2주 만에 3명의 유료 사용자를 확보했습니다. 차이점은 AI가 아니었습니다. 바로 마찰(friction)이었습니다. 사용자들은 또 다른 앱을 열고 싶어 하지 않았습니다. 그들은 이미 머물고 있는 곳에서 배우고 싶어 했습니다. 이로 인해 나는 대화 메모리(conversation memory), 특히 WhatsApp의 상태 비저장(stateless) API와 나의 0원 규모의 VC 예산 상황에서의 메모리 방식을 재고해야 했습니다.
웹 앱의 치명적인 결함: 새로운 습관 강요
웹 앱의 아키텍처는 표준적이었습니다: React 프론트엔드, FastAPI 백엔드, 사용자 데이터를 위한 PostgreSQL, 그리고 벡터 검색을 위한 Pinecone이었습니다. 각 사용자 상호작용은 LLM 호출(초기에는 OpenAI, 나중에는 커스텀 라우터를 통한 Groq와 Claude의 혼합)을 트리거했습니다. 메모리는 최근 대화 턴(conversation turns)을 임베딩(embedding)하고 Pinecone을 쿼리하여 관련 있는 과거 상호작용을 검색하는 방식으로 처리되었습니다. 기술적으로는 작동했습니다. 대화는 일관성이 있었습니다. 문제는 채택(adoption)이었습니다.
사용자들은 가입하고 10~15분 동안 채팅을 한 뒤 사라지곤 했습니다. 어렵게 얻을 수 있었던 피드백은 항상 같았습니다: "좋긴 한데, 이미 Duolingo를 쓰고 있어요" 또는 "여는 걸 자꾸 까먹어요"와 같은 내용이었습니다. espaluz.com을 방문해야 한다는 것을 기억해야 하는 인지 부하(cognitive load)가 너무 높았습니다. 나는 기존 솔루션보다 수십 배 더 나은 제품이 아님에도 불구하고, 사용자들에게 일상적인 루틴을 바꾸라고 요구하고 있었던 것입니다.
WhatsApp으로의 피벗: 제약 조건 수용하기
WhatsApp의 API는 잔혹할 정도로 단순하며 상태를 유지하지 않습니다 (stateless). 모든 수신 메시지는 새로운 이벤트입니다. 내장된 세션 관리 (session management)도 없고, 풍부한 UI도 없습니다. 오직 텍스트뿐입니다. 이는 예산이 전혀 없는 상태에서 검증하려던 제품에 기존의 Pinecone 기반 메모리 시스템이 과하고 너무 비싸다는 것을 의미했습니다. 저에게는 저렴하고 견고하며, 단 몇 분이 아니라 며칠에 걸쳐 문맥 (context)을 유지할 수 있는 AI 언어 학습 WhatsApp 대화 메모리 시스템이 필요했습니다.
저는 유료 벡터 저장소 (vector store) 없이, 전적으로 인메모리 (in-memory) 및 디스크 (on-disk) 방식의 2계층 메모리 접근 방식을 결정했습니다.
Layer 1: 단기, 인메모리 컨텍스트 윈도우 (Short-Term, In-Memory Context Window)
즉각적인 대화 흐름을 위해, 저는 단순한 롤링 컨텍스트 윈도우 (rolling context window)를 구현했습니다. 각 사용자의 활성 대화 기록(토큰 수에 따라 마지막 10~15회 차례)은 FastAPI 애플리케이션의 메모리 내 Python 딕셔너리 (dictionary)에 저장됩니다. 이 딕셔너리는 WhatsApp 사용자 ID를 키 (key)로 사용합니다.
메시지가 도착하면:
- 딕셔너리에서 사용자의 현재 컨텍스트 리스트를 가져옵니다.
- 새로운 사용자 메시지를 추가합니다.
- 전체 컨텍스트 리스트를 LLM에 전달합니다.
- LLM의 응답을 컨텍스트 리스트에 추가합니다.
- 가장 오래된 항목을 제거하며, 허용된 최대 차례/토큰 수에 맞춰 컨텍스트 리스트를 다듬습니다 (trim).
이 단기 메모리는 휘발성입니다. FastAPI 프로세스가 재시작되거나 사용자가 너무 오랫동안 비활성 상태인 경우(예: 30분), 이 메모리는 손실됩니다. 여기서 Layer 2가 등장합니다.
Layer 2: 장기, 요약된 메모리 (PostgreSQL) (Long-Term, Summarized Memory)
며칠 또는 몇 주에 걸쳐 진정한 대화의 연속성을 제공하기 위해, 저는 PostgreSQL을 사용하여 장기 메모리 시스템을 구현했습니다. 바로 이 지점에서 "벡터 저장소 없음"이라는 제약 조건이 설계의 동력이 되었습니다.
모든 메시지를 임베딩 (Embedding) 하는 대신, 저는 전체 대화 세그먼트 (Conversation segments) 를 요약합니다. 매 10~15회 대화가 오가거나 (또는 30분과 같은 비활성 기간이 지난 후), 현재의 단기 대화 기록은 특정 프롬프트 (Prompt) 와 함께 LLM (Large Language Model) 에 전달됩니다: "다음 대화 세그먼트에서 다뤄진 핵심 학습 포인트, 도입된 어휘, 그리고 논의된 문법 개념을 요약하세요. 사용자가 어려워했거나 학습한 내용에 집중하세요. 불렛 포인트 (Bullet points) 형식으로 출력하세요."
예시 프롬프트:
당신은 스페인어 학습 대화를 요약하는 임무를 맡은 AI 어시스턴트입니다.
다음 대화 세그먼트에서 다뤄진 핵심 학습 포인트, 도입된 어휘, 그리고 논의된 문법 개념을 요약하세요.
사용자가 어려워했거나 학습한 내용에 집중하세요. 불렛 포인트 형식으로 출력하세요.
...
LLM의 요약본(통상 100~300 토큰)은 이후 PostgreSQL의 conversation_summaries 테이블에 사용자 ID 및 타임스탬프와 연결되어 저장됩니다.
사용자가 오랜 부재 후 돌아오거나 단기 메모리가 소실된 경우:
- 시스템은 해당 사용자에 대한 가장 최근의 요약본 3~5개를 PostgreSQL에서 쿼리 (Query) 합니다.
- 이 요약본들은 LLM에 전달되는 프롬프트의 앞에 추가되어, AI에게 과거의 학습 내용을 효과적으로 "상기" 시킵니다.
- 그 후 LLM은 이 장기 컨텍스트 (Long-term context) 를 통합하여 응답을 생성합니다.
이 접근 방식은 벡터 저장소 (Vector store) 보다 훨씬 저렴합니다:
- 매 대화마다 발생하는 임베딩 비용 없음: 훨씬 드물게 발생하는 요약 시에만 비용이 발생합니다.
- 벡터 데이터베이스 호스팅 비용 없음: 표준 PostgreSQL로 충분합니다.
- 검색을 위한 LLM 호출 감소: 요약본은 복잡한 벡터 검색이 아닌 단순한 SQL 쿼리를 통해 검색됩니다.
트레이드오프 (Trade-off) 는 정밀도입니다. AI가 몇 주 전의 _모든 구체적인 문구_를 기억하지는 못하지만, 다뤄진 내용의 _요지 (Gist)_를 기억하며, 이는 언어 학습에 있어 종종 충분합니다.
Oracle Cloud Infrastructure: 프리 티어 (Free Tier) 의 이점
나의 전체 백엔드는 Oracle Cloud Infrastructure (OCI) Free Tier에서 실행됩니다. 이는 자본이 제한된 (bootstrapped) 프로젝트에게 매우 중요합니다.
- Always Free Compute: Ampere A1 인스턴스 (4 OCPU, 24 GB RAM)가 나의 FastAPI 애플리케이션, Groq/Claude 라우터, 그리고 WhatsApp 웹훅 (webhook)을 실행합니다.
- Always Free Autonomous Database: PostgreSQL 호환 인스턴스가 모든 사용자 데이터와 대화 요약본을 처리합니다.
- Network Egress: 프리 티어에는 매월 10 TB의 데이터 송신 (egress)이 포함되어 있으며, 이는 현재 나의 트래픽을 감당하기에 충분하고도 남습니다.
이 설정을 통해 인프라 비용은 월 $0이며, 덕분에 나는 한정된 자금을 LLM API 호출에 집중할 수 있습니다.
3명의 유료 사용자 vs 100명의 무료 가입자
WhatsApp 에이전트의 유료 사용자 3명은 웹 앱의 무료 가입자 100명보다 나에게 훨씬 더 많은 것을 가르쳐 주었습니다.
- "또 다른 앱을 열고 싶지 않아요." 이것이 가장 흔한 피드백이었습니다. WhatsApp은 그들이 이미 시간을 보내고 있는 공간입니다. 컨텍스트 스위칭 (context switching)의 번거로움은 웹 앱에 있어 결정적인 결격 사유였습니다.
- "지난주에 배운 것을 기억하고 있어요." 2계층 메모리, 특히 PostgreSQL 요약본은 엄청난 차이를 만들어냈습니다. 사용자들은 하루에 10분씩, 일주일에 몇 번만 채팅하더라도 연속적인 학습 여정을 경험한다고 느꼈습니다. 이것이 그들을 유료로 전환시킨 핵심 가치 제안 (value proposition)이었습니다.
- "주머니 속에 튜터가 있는 것 같아요." 메모리와 결합된 대화형 특성은 개인화된 교육의 느낌을 주었습니다. 그들은 단순히 어휘를 암기하는 것이 아니라, 자신의 진도에 맞춰 적응하는 대화를 나누고 있었습니다.
이 사용자들은 제품이 그들의 삶에 매끄럽게 통합되어 실질적인 가치, 즉 추가적인 노력 없이도 일관되고 개인화된 스페인어 연습을 제공했기 때문에 월 $9를 지불할 의사가 있었습니다. 웹 앱은 기술적인 정교함에도 불구하고, 사용자 행동을 간과했기 때문에 실패했습니다.
현재 저의 에이전트 라우팅(agent routing)은 단순한 Python 딕셔너리를 사용하여 사용자 ID를 선호하는 LLM 제공업체(속도를 위한 Groq, 복잡한 설명을 위한 Claude)에 매핑합니다. 이를 통해 비용과 성능에 따라 동적으로 전환할 수 있으며, 특정 사용자 세그먼트에 대해 서로 다른 모델을 A/B 테스트할 수도 있습니다.
자주 묻는 질문 (Frequently Asked Questions)
Q: 요약본 대신 원문 메시지 기록을 위해 단순한 데이터베이스 테이블을 사용하지 않는 이유는 무엇인가요?
A: 원문 메시지 기록을 저장하면 LLM의 컨텍스트 윈도우(context window) 제한을 빠르게 초과하게 되며, 검색을 위해 비용이 많이 드는 임베딩(embedding)과 벡터 검색(vector search)이 필요하게 됩니다. 요약본은 압축적이고 저장 비용이 저렴하며, 학습과 관련된 상위 수준의 컨텍스트를 제공하여 LLM 프롬프트의 토큰 비용을 줄여줍니다.
Q: WhatsApp에서 동일한 사용자의 동시 메시지는 어떻게 처리하나요?
A: WhatsApp의 API는 메시지를 순차적으로 전송합니다. 저의 FastAPI 애플리케이션은 이를 순서대로 처리합니다. 매우 높은 동시성이 발생하는 경우에는 메시지 큐(Kafka 또는 RabbitMQ와 같은)가 필요하겠지만, 단일 사용자의 경우 인메모리(in-memory) 딕셔너리와 데이터베이스 업데이트만으로도 충분히 빠르고 적절합니다.
Q: LLM 요약이 부실하거나 핵심 세부 사항을 놓치면 어떻게 하나요?
A: 이는 위험 요소입니다. 저는 요약 프롬프트(summary prompt)를 지속적으로 개선하고 요약 품질을 모니터링합니다. 중요한 정보의 경우, 특정 요소(예: "사용자가 'ser'와 'estar'의 차이로 어려움을 겪음")가 항상 포함되도록 요약 프롬프트에 특정 태그나 키워드를 추가할 수 있습니다.
Q: VC(벤처 캐피털) 투자 없이 LLM API 호출 비용을 어떻게 관리하나요?
A: 저렴한 비용과 빠른 속도 덕분에 대부분의 상호작용에는 Groq을 공격적으로 사용합니다. 더 복잡한 설명이 필요하거나 Groq이 어려워하는 경우에는 Claude 3 Haiku를 사용합니다. 또한 응답에 토큰 제한을 구현하고 효율적인 프롬프트 엔지니어링(prompt engineering)을 사용하여 입력 토큰을 최소화합니다. 현재 저의 LLM 지출은 월 $50 미만입니다.
Q: 이 설정을 구축하면서 직면한 가장 큰 기술적 과제는 무엇이었나요?
A: WhatsApp API 호출 및 LLM 상호작용에 대한 견고한 에러 처리(error handling)와 재시도 메커니즘(retry mechanisms)을 보장하는 것이었습니다. WhatsApp은 일시적인 문제가 발생할 수 있으며, LLM은 가끔 잘못된 형식의 JSON을 반환하거나 속도 제한(rate limit)에 걸릴 수 있습니다. 안정성을 위해 지수 백오프(exponential backoff)와 명확한 로깅(logging)을 구현하는 것이 매우 중요했습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기